added method to intercept tls handshakes, and to verify certificates

This commit is contained in:
Emiel Bruijntjes 2018-03-10 00:59:14 +01:00
parent 4c2b8ff68e
commit 367de51d77
3 changed files with 142 additions and 30 deletions

103
README.md
View File

@ -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 <amqpcpp.h>
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

View File

@ -6,7 +6,7 @@
* class.
*
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
* @copyright 2015 Copernica BV
* @copyright 2015 - 2018 Copernica BV
*/
/**
@ -14,6 +14,11 @@
*/
#pragma once
/**
* Dependencies
*/
#include <openssl/ssl.h>
/**
* 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) {}

View File

@ -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);