diff --git a/src/linux_tcp/openssl.cpp b/src/linux_tcp/openssl.cpp index 9adfccf..9ee877b 100644 --- a/src/linux_tcp/openssl.cpp +++ b/src/linux_tcp/openssl.cpp @@ -354,6 +354,20 @@ void ERR_clear_error() return func(); } +/** + * Print errors via a callback + * @param cb + * @param u + */ +void ERR_print_errors_cb(int (*cb)(const char *str, size_t len, void *u), void *u) +{ + // the actual function + static Function func(handle, "ERR_print_errors_cb"); + + // call the openssl function + func(cb, u); +} + /** * End of namespace */ diff --git a/src/linux_tcp/openssl.h b/src/linux_tcp/openssl.h index 977f913..87c6bb6 100644 --- a/src/linux_tcp/openssl.h +++ b/src/linux_tcp/openssl.h @@ -53,6 +53,7 @@ long SSL_ctrl(SSL *ssl, int cmd, long larg, void *parg); long SSL_CTX_ctrl(SSL_CTX *ctx, int cmd, long larg, void *parg); int SSL_CTX_set_default_verify_paths(SSL_CTX *ctx); void ERR_clear_error(void); +void ERR_print_errors_cb(int (*cb)(const char *str, size_t len, void *u), void *u); /** * End of namespace diff --git a/src/linux_tcp/sslconnected.h b/src/linux_tcp/sslconnected.h index d71d279..78fc8e8 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 @@ -133,17 +134,17 @@ private: case SSL_ERROR_WANT_READ: // remember state _state = state; - + // the operation must be repeated when readable _parent->onIdle(this, _socket, readable); - + // allow chaining return true; - + case SSL_ERROR_WANT_WRITE: // remember state _state = state; - + // wait until socket becomes writable again _parent->onIdle(this, _socket, readable | writable); @@ -155,19 +156,24 @@ private: case SSL_ERROR_NONE: // we're ready for the next instruction from userspace _state = state_idle; - + // if we still have an outgoing buffer we want to send out data, otherwise we just read _parent->onIdle(this, _socket, _out ? readable | writable : readable); // nothing is wrong, we are done return true; - + default: // we are now in an error state _state = state_error; - // report an error to user-space - _parent->onError(this, "ssl protocol error"); + { + // get a human-readable error string + const SslErrorPrinter message{error}; + + // report an error to user-space + _parent->onError(this, message.data()); + } // ssl level error, we have to tear down the tcp connection return false; @@ -304,7 +310,6 @@ private: // proceed with the write operation or the event loop return _out && isWritable() ? write(monitor) : proceed(); } - public: /** diff --git a/src/linux_tcp/sslerrorprinter.cpp b/src/linux_tcp/sslerrorprinter.cpp new file mode 100644 index 0000000..9ad24d9 --- /dev/null +++ b/src/linux_tcp/sslerrorprinter.cpp @@ -0,0 +1,100 @@ +/** + * SslErrorPrinter.cpp + * + * Implementation of SslErrorPrinter + * + * @copyright 2021 copernica BV + */ + +/** + * Dependencies + */ +#include "sslerrorprinter.h" +#include + +/** + * Begin namespace + */ +namespace AMQP { + +/** + * Callback used for ERR_print_errors_cb + * @param str The string + * @param len The length + * @param ctx The context (this ptr) + * @return always 0 to signal to OpenSSL to continue + */ +int sslerrorprintercallback(const char *str, size_t len, void *ctx) +{ + // Cast to ourselves and store the error line. OpenSSL adds a newline to every error line. + static_cast(ctx)->_message.append(str, len); + + // continue with the next message + return 0; +} + +/** + * Constructor + * @param retval return value of SSL_get_error (must be a proper error) + */ +SslErrorPrinter::SslErrorPrinter(int retval) +{ + // 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) _message = "SSL_R_UNEXPECTED_EOF_WHILE_READING"; + + // Otherwise we ask the OS for a description of the error. + else _message = ::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: + + // collect all error lines + OpenSSL::ERR_print_errors_cb(&sslerrorprintercallback, this); + + // remove the last newline + if (!_message.empty() && _message.back() == '\n') _message.pop_back(); + + // done + break; + + default: + // we don't know what kind of error this is + _message = "unknown ssl error"; + + // done + break; + } +} + +/** + * data ptr (guaranteed null-terminated) + * @return const char * + */ +const char *SslErrorPrinter::data() const noexcept { return _message.data(); } + +/** + * length of the string + * @return size_t + */ +std::size_t SslErrorPrinter::size() const noexcept { return _message.size(); } + +/** + * End of namespace AMQP + */ +} diff --git a/src/linux_tcp/sslerrorprinter.h b/src/linux_tcp/sslerrorprinter.h new file mode 100644 index 0000000..03ddb80 --- /dev/null +++ b/src/linux_tcp/sslerrorprinter.h @@ -0,0 +1,72 @@ +/** + * SslErrorPrinter.h + * + * Flushes the SSL error stack to a string. + * 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 + +/** + * Begin namespace + */ +namespace AMQP { + +/** + * Class declaration + */ +class SslErrorPrinter final +{ +public: + /** + * Constructor + * @param retval return value of SSL_get_error (must be a proper error) + */ + SslErrorPrinter(int retval); + + /** + * data ptr (guaranteed null-terminated) + * @return const char * + */ + const char *data() const noexcept; + + /** + * length of the string + * @return size_t + */ + std::size_t size() const noexcept; + +private: + /** + * The error message + * @var std::string + */ + std::string _message; + + /** + * Callback used for ERR_print_errors_cb + * @param str The string + * @param len The length + * @param ctx The context (this ptr) + * @return always 0 to signal to OpenSSL to continue + */ + friend int sslerrorprintercallback(const char *str, size_t len, void *ctx); +}; + +/** + * End of namespace AMQP + */ +} diff --git a/src/linux_tcp/sslhandshake.h b/src/linux_tcp/sslhandshake.h index 33092b1..d04e3b1 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,16 @@ 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 message{retval}; + // 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 +187,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); } }