added method to intercept tls handshakes, and to verify certificates
This commit is contained in:
parent
4c2b8ff68e
commit
367de51d77
103
README.md
103
README.md
|
|
@ -64,9 +64,14 @@ Then check out our other commercial and open source solutions:
|
||||||
|
|
||||||
INSTALLING
|
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.
|
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.
|
On Windows you are required to define `NOMINMAX` when compiling code that includes public AMQP-CPP header files.
|
||||||
|
|
||||||
## CMake
|
## 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
|
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
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake .. [-DAMQP-CPP_AMQBUILD_SHARED] [-DAMQP-CPP_LINUX_TCP]
|
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_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?
|
AMQP-CPP_LINUX_TCP | OFF | Should the Linux-only TCP module be built?
|
||||||
|
|
||||||
## Make
|
## Using make
|
||||||
|
|
||||||
Installing the library is as easy
|
Compiling and installing AMQP-CPP with make is as easy as running `make` and
|
||||||
as running `make` and `make install`. This will install the full version of
|
then `make install`. This will install the full version of AMQP-CPP, including
|
||||||
the AMQP-CPP, including the system specific TCP module. If you do not need the
|
the system specific TCP module. If you do not need the additional TCP module
|
||||||
additional TCP module (because you take care of handling the network stuff
|
(because you take care of handling the network stuff yourself), you can also
|
||||||
yourself), you can also compile a pure form of the library. Use `make pure`
|
compile a pure form of the library. Use `make pure` and `make install` for that.
|
||||||
and `make install` for that.
|
|
||||||
|
|
||||||
When you compile an application that uses the AMQP-CPP library, do not
|
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.
|
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
|
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
|
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
|
thread for running an asynchronous and non-blocking DNS hostname lookup, and it
|
||||||
optionally dynamically opens the openssl library if a secure connection to
|
may dynamically look up functions from the openssl library if a secure connection
|
||||||
RabbitMQ has to be set up.
|
to RabbitMQ has to be set up.
|
||||||
|
|
||||||
|
|
||||||
HOW TO USE AMQP-CPP
|
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())
|
// init the SSL library (this works for openssl 1.1, for openssl 1.0 use SSL_library_init())
|
||||||
OPENSSL_init_ssl(0, NULL);
|
OPENSSL_init_ssl(0, NULL);
|
||||||
|
|
||||||
// address of the server
|
// address of the server (secure!)
|
||||||
AMQP::Address address("amqps://guest:guest@localhost/vhost");
|
AMQP::Address address("amqps://guest:guest@localhost/vhost");
|
||||||
|
|
||||||
// create a AMQP connection object
|
// 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:
|
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
|
(1) you must link your application with the -lssl flag (or use dlopen()), and (2)
|
||||||
the openssl library by calling OPENSSL_init_ssl(). This initializating must take
|
you must initialize the openssl library by calling OPENSSL_init_ssl(). This
|
||||||
place before you let you application connect to RabbitMQ. This is necessary
|
initializating must take place before you let you application connect to RabbitMQ.
|
||||||
because AMQP-CPP needs access to the openssl library, which it needs for setting up
|
This is necessary because AMQP-CPP needs access to the openssl library to set up
|
||||||
secure connections.
|
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
|
EXISTING EVENT LOOPS
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
* class.
|
* class.
|
||||||
*
|
*
|
||||||
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
|
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
|
||||||
* @copyright 2015 Copernica BV
|
* @copyright 2015 - 2018 Copernica BV
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -14,6 +14,11 @@
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up namespace
|
* Set up namespace
|
||||||
*/
|
*/
|
||||||
|
|
@ -35,9 +40,33 @@ public:
|
||||||
*/
|
*/
|
||||||
virtual ~TcpHandler() = default;
|
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
|
* 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 connection The connection that suggested a heartbeat interval
|
||||||
* @param interval The suggested interval from the server
|
* @param interval The suggested interval from the server
|
||||||
* @return uint16_t The interval to use
|
* @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
|
* @param connection The TCP connection
|
||||||
*/
|
*/
|
||||||
virtual void onConnected(TcpConnection *connection) {}
|
virtual void onConnected(TcpConnection *connection) {}
|
||||||
|
|
|
||||||
|
|
@ -53,16 +53,34 @@ private:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Report a new state
|
* Report a new state
|
||||||
* @param state
|
* @param monitor
|
||||||
* @return TcpState
|
* @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;
|
_socket = -1;
|
||||||
|
|
||||||
// done
|
// if connection is allowed, we move to the next state
|
||||||
return 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);
|
int result = OpenSSL::SSL_do_handshake(_ssl);
|
||||||
|
|
||||||
// if the connection succeeds, we can move to the ssl-connected state
|
// 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
|
// error was returned, so we must investigate what is going on
|
||||||
auto error = OpenSSL::SSL_get_error(_ssl, result);
|
auto error = OpenSSL::SSL_get_error(_ssl, result);
|
||||||
|
|
@ -211,7 +229,7 @@ public:
|
||||||
int result = OpenSSL::SSL_do_handshake(_ssl);
|
int result = OpenSSL::SSL_do_handshake(_ssl);
|
||||||
|
|
||||||
// if the connection succeeds, we can move to the ssl-connected state
|
// 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
|
// error was returned, so we must investigate what is going on
|
||||||
auto error = OpenSSL::SSL_get_error(_ssl, result);
|
auto error = OpenSSL::SSL_get_error(_ssl, result);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue