diff --git a/README.md b/README.md index a7c3659..e235b25 100644 --- a/README.md +++ b/README.md @@ -64,9 +64,14 @@ Then check out our other commercial and open source solutions: INSTALLING ========== -AMQP-CPP comes with an optional Linux-only TCP module that takes care of the network part required for the AMQP-CPP core library. If you use this module, you are required to link with `pthread`. -There are two methods to compile AMQP-CPP: CMake and Make. CMake is platform portable, but the Makefile only works on Linux. Building of a shared library is currently not supported on Windows. +AMQP-CPP comes with an optional Linux-only TCP module that takes care of the +network part required for the AMQP-CPP core library. If you use this module, you +are required to link with `pthread` and `dl`. + +There are two methods to compile AMQP-CPP: CMake and Make. CMake is platform portable +and works on all systems, while the Makefile only works on Linux. Building of a shared +library is currently not supported on Windows. After building there are two relevant files to include when using the library. @@ -77,9 +82,14 @@ After building there are two relevant files to include when using the library. On Windows you are required to define `NOMINMAX` when compiling code that includes public AMQP-CPP header files. -## CMake -The CMake file supports both building and installing. You can choose not to use the install functionality, and instead manually use the build output at `bin/`. Keep in mind that the TCP module is only supported for Linux. An example install method would be: -``` bash +## Using cmake + +The CMake file supports both building and installing. You can choose not to use +the install functionality, and instead manually use the build output at `bin/`. Keep +in mind that the TCP module is only supported for Linux. An example install method +would be: + +```bash mkdir build cd build cmake .. [-DAMQP-CPP_AMQBUILD_SHARED] [-DAMQP-CPP_LINUX_TCP] @@ -91,22 +101,21 @@ cmake --build .. --target install AMQP-CPP_BUILD_SHARED | OFF | Static lib(ON) or shared lib(OFF)? Shared is not supported on Windows. AMQP-CPP_LINUX_TCP | OFF | Should the Linux-only TCP module be built? -## Make +## Using make -Installing the library is as easy -as running `make` and `make install`. This will install the full version of -the AMQP-CPP, including the system specific TCP module. If you do not need the -additional TCP module (because you take care of handling the network stuff -yourself), you can also compile a pure form of the library. Use `make pure` -and `make install` for that. +Compiling and installing AMQP-CPP with make is as easy as running `make` and +then `make install`. This will install the full version of AMQP-CPP, including +the system specific TCP module. If you do not need the additional TCP module +(because you take care of handling the network stuff yourself), you can also +compile a pure form of the library. Use `make pure` and `make install` for that. When you compile an application that uses the AMQP-CPP library, do not forget to link with the library. For gcc and clang the linker flag is -lamqpcpp. If you use the fullblown version of AMQP-CPP (with the TCP module), you also need to pass the -lpthread and -ldl linker flags, because the TCP module uses a thread for running an asynchronous and non-blocking DNS hostname lookup, and it -optionally dynamically opens the openssl library if a secure connection to -RabbitMQ has to be set up. +may dynamically look up functions from the openssl library if a secure connection +to RabbitMQ has to be set up. HOW TO USE AMQP-CPP @@ -408,7 +417,7 @@ server using the amqps:// protocol: // init the SSL library (this works for openssl 1.1, for openssl 1.0 use SSL_library_init()) OPENSSL_init_ssl(0, NULL); -// address of the server +// address of the server (secure!) AMQP::Address address("amqps://guest:guest@localhost/vhost"); // create a AMQP connection object @@ -416,11 +425,65 @@ AMQP::TcpConnection connection(&myHandler, address); ```` There are two things to take care of if you want to create a secure connection: -(1) you must link your application with the -lssl flag, and (2) you must initialize -the openssl library by calling OPENSSL_init_ssl(). This initializating must take -place before you let you application connect to RabbitMQ. This is necessary -because AMQP-CPP needs access to the openssl library, which it needs for setting up -secure connections. +(1) you must link your application with the -lssl flag (or use dlopen()), and (2) +you must initialize the openssl library by calling OPENSSL_init_ssl(). This +initializating must take place before you let you application connect to RabbitMQ. +This is necessary because AMQP-CPP needs access to the openssl library to set up +secure connections. It can only access this library if you have linked your +application with this library, or if you have loaded this library at runtime +using dlopen()). + +If you do not want to link your application with openssl, you can also load the +openssl library at runtime, and pass in the pointer to the handler to AMQP-CPP: + +````c++ +// dynamically open the openssl library +void *handle = dlopen("/path/to/openssl.so", RTLD_LAZY); + +// tell AMQP-CPP library where the handle to openssl can be found +AMQP::openssl(handle); + +// @todo call functions to initialize openssl, and create the AMQP connection +// (see exampe above) +```` + +By itself, AMQP-CPP does not check if the created TLS connection is sufficient +secure. Whether the certificate is expired, self-signed, missing or invalid: for +AMQP-CPP it all doesn't matter and the connection is simply permitted. If you +want to be more strict (for example: if you want to verify the server's certificate), +you must do this yourself by implementing the "onSecured()" method in your handler +object: + +````c++ +#include + +class MyTcpHandler : public AMQP::TcpHandler +{ + /** + * Method that is called right after the TLS connection has been created. + * In this method you can check the connection properties (like the certificate) + * and return false if you find it not secure enough + * @param connection the connection that has just completed the tls handshake + * @param ssl SSL structure from the openssl library + * @return bool true if connection is secure enough to start the AMQP protocol + */ + virtual bool onSecure(AMQP::TcpConnection *connection, const SSL *ssl) override + { + // @todo call functions from the openssl library to check the certificate, + // like SSL_get_peer_certificate() or SSL_get_verify_result(). + // For now we always allow the connection to proceed + return true; + } + + /** + * All other methods (like onConnected(), onError(), etc) are left out of this + * example, but would be here if this was an actual user space handler class. + */ +}; +```` + +The SSL pointer that is passed to the onSecured() method refers to the "SSL" +structure from the openssl library. EXISTING EVENT LOOPS diff --git a/include/amqpcpp/linux_tcp/tcphandler.h b/include/amqpcpp/linux_tcp/tcphandler.h index 5549425..9b0c87d 100644 --- a/include/amqpcpp/linux_tcp/tcphandler.h +++ b/include/amqpcpp/linux_tcp/tcphandler.h @@ -6,7 +6,7 @@ * class. * * @author Emiel Bruijntjes - * @copyright 2015 Copernica BV + * @copyright 2015 - 2018 Copernica BV */ /** @@ -14,6 +14,11 @@ */ #pragma once +/** + * Dependencies + */ +#include + /** * Set up namespace */ @@ -35,9 +40,33 @@ public: */ virtual ~TcpHandler() = default; + /** + * Method that is called after a TCP connection has been set up and the initial + * TLS handshake is finished too, but right before the AMQP login handshake is + * going to take place and the first data is going to be sent over the connection. + * This method allows you to inspect the TLS certificate and other connection + * properties, and to break up the connection if you find it not secure enough. + * The default implementation considers all connections to be secure, even if the + * connection has a self-signed or even invalid certificate. To be more strict, + * override this method, inspect the certificate and return false if you do not + * want to use the connection. The passed in SSL pointer is a pointer to a SSL + * structure from the openssl library. This method is only called for secure + * connections (connection with an amqps:// address). + * @param connection The connection for which TLS was just started + * @param ssl Pointer to the SSL structure that can be inspected + * @return bool True to proceed / accept the connection, false to break up + */ + virtual bool onSecured(TcpConnection *connection, const SSL *ssl) + { + // default implementation: do not inspect anything, just allow the connection + return true; + } + /** * Method that is called when the heartbeat frequency is negotiated - * between the server and the client. + * between the server and the client. Applications can override this method + * if they want to use a different heartbeat interval (for example: return 0 + * to disable heartbeats) * @param connection The connection that suggested a heartbeat interval * @param interval The suggested interval from the server * @return uint16_t The interval to use @@ -51,7 +80,9 @@ public: } /** - * Method that is called when the TCP connection ends up in a connected state + * Method that is called when the AMQP connection ends up in a connected state + * This method is called after the TCP connection has been set up, the (optional) + * secure TLS connection, and the AMQP login handshake has been completed. * @param connection The TCP connection */ virtual void onConnected(TcpConnection *connection) {} diff --git a/src/linux_tcp/sslhandshake.h b/src/linux_tcp/sslhandshake.h index b593381..34d379e 100644 --- a/src/linux_tcp/sslhandshake.h +++ b/src/linux_tcp/sslhandshake.h @@ -53,16 +53,34 @@ private: /** * Report a new state - * @param state + * @param monitor * @return TcpState */ - TcpState *nextstate(TcpState *state) + TcpState *nextstate(const Monitor &monitor) { - // forget the socket to prevent that it is closed by the destructor + // check if the handler allows the connection + bool allowed = _handler->onSecured(_connection, _ssl); + + // leap out if the user space function destructed the object + if (!monitor.valid()) return nullptr; + + // copy the socket because we might forget it + auto socket = _socket; + + // forget the socket member to prevent that it is closed by the destructor _socket = -1; - // done - return state; + // if connection is allowed, we move to the next state + if (allowed) return new SslConnected(_connection, socket, std::move(_ssl), std::move(_out), _handler); + + // report that the connection is broken + _handler->onError(_connection, "TLS connection has been blocked by application level checks"); + + // the onError method could have destructed this object + if (!monitor.valid()) return nullptr; + + // shutdown the connection + return new SslShutdown(_connection, socket, std::move(_ssl), true, _handler); } /** @@ -170,7 +188,7 @@ public: int result = OpenSSL::SSL_do_handshake(_ssl); // if the connection succeeds, we can move to the ssl-connected state - if (result == 1) return nextstate(new SslConnected(_connection, _socket, std::move(_ssl), std::move(_out), _handler)); + if (result == 1) return nextstate(monitor); // error was returned, so we must investigate what is going on auto error = OpenSSL::SSL_get_error(_ssl, result); @@ -211,7 +229,7 @@ public: int result = OpenSSL::SSL_do_handshake(_ssl); // if the connection succeeds, we can move to the ssl-connected state - if (result == 1) return nextstate(new SslConnected(_connection, _socket, std::move(_ssl), std::move(_out), _handler)); + if (result == 1) return nextstate(monitor); // error was returned, so we must investigate what is going on auto error = OpenSSL::SSL_get_error(_ssl, result);