Skip to content

Commit

Permalink
swim: implement and expose transport-level encryption
Browse files Browse the repository at this point in the history
SWIM is going to be used in and between datacenters, which means,
that its packets will go through public networks. Therefore raw
SWIM packets are vulnerable to attacks.

An attacker can do any and all of the following things:

  1) Extract secret information from member payloads, like
     credentials to Tarantool binary ports;

  2) Change UUIDs and addresses in the packets and break a
     topology;

  3) Catch the packets and pretend being a Tarantool instance,
     which could lead to undefined behaviour depending on an
     application logic.

SWIM packets need a protection layer. This commit introduces it.
SWIM transport level allows to choose an encryption algorithm
with a private key to encrypt each packet with that key.

Besides, each packet is encrypted using a random public key
prepended to the packet.

SWIM now provides a public API to choose an encryption algorithm
and a private key.

Part of #3234
  • Loading branch information
Gerold103 committed May 21, 2019
1 parent f77f4b9 commit 6137c19
Show file tree
Hide file tree
Showing 11 changed files with 257 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/lib/swim/CMakeLists.txt
Expand Up @@ -5,7 +5,7 @@ set(lib_swim_ev_sources swim_ev.c)
set_source_files_compile_flags(${lib_swim_sources} ${lib_swim_udp_sources}
${lib_swim_ev_sources})
add_library(swim STATIC ${lib_swim_sources})
target_link_libraries(swim core misc uuid)
target_link_libraries(swim core misc uuid crypto)
add_library(swim_udp STATIC ${lib_swim_udp_sources})
target_link_libraries(swim_udp core)
add_library(swim_ev STATIC ${lib_swim_ev_sources})
Expand Down
8 changes: 8 additions & 0 deletions src/lib/swim/swim.c
Expand Up @@ -1821,6 +1821,14 @@ swim_cfg(struct swim *swim, const char *uri, double heartbeat_rate,
return 0;
}

int
swim_set_codec(struct swim *swim, enum crypto_algo algo, enum crypto_mode mode,
const char *key, int key_size)
{
return swim_scheduler_set_codec(&swim->scheduler, algo, mode,
key, key_size);
}

bool
swim_is_configured(const struct swim *swim)
{
Expand Down
18 changes: 18 additions & 0 deletions src/lib/swim/swim.h
Expand Up @@ -32,6 +32,7 @@
*/
#include <stdbool.h>
#include <stdint.h>
#include "crypto/crypto.h"
#include "swim_constants.h"

#if defined(__cplusplus)
Expand Down Expand Up @@ -104,6 +105,23 @@ swim_cfg(struct swim *swim, const char *uri, double heartbeat_rate,
int
swim_set_payload(struct swim *swim, const char *payload, int payload_size);

/**
* Set SWIM codec to encrypt/decrypt messages.
* @param swim SWIM instance to set codec for.
* @param algo Cipher algorithm.
* @param mode Algorithm mode.
* @param key Private key of the chosen algorithm. It is used to
* encrypt/decrypt messages, and should be the same on all
* cluster nodes. Note that it can be changed, but it shall
* be done on all cluster nodes. Otherwise the nodes will
* not understand each other. There is also a public key
* usually, but it is generated randomly inside SWIM.
* @param key_size Key size in bytes.
*/
int
swim_set_codec(struct swim *swim, enum crypto_algo algo, enum crypto_mode mode,
const char *key, int key_size);

/**
* Stop listening and broadcasting messages, cleanup all internal
* structures, free memory.
Expand Down
2 changes: 2 additions & 0 deletions src/lib/swim/swim_ev.h
Expand Up @@ -66,4 +66,6 @@ swim_ev_timer_stop(struct ev_loop *loop, struct ev_timer *watcher);

#define swim_ev_io_set ev_io_set

#define swim_ev_set_cb ev_set_cb

#endif /* TARANTOOL_SWIM_EV_H_INCLUDED */
139 changes: 121 additions & 18 deletions src/lib/swim/swim_io.c
Expand Up @@ -269,31 +269,20 @@ swim_scheduler_fd(const struct swim_scheduler *scheduler)
return scheduler->transport.fd;
}

/**
* Dispatch a next output event. Build packet meta and send the
* packet.
*/
static void
swim_scheduler_on_output(struct ev_loop *loop, struct ev_io *io, int events);

/**
* Dispatch a next input event. Unpack meta, forward a packet or
* propagate further to protocol logic.
*/
static void
swim_scheduler_on_input(struct ev_loop *loop, struct ev_io *io, int events);

void
swim_scheduler_create(struct swim_scheduler *scheduler,
swim_scheduler_on_input_f on_input)
{
swim_ev_init(&scheduler->output, swim_scheduler_on_output);
scheduler->output.data = (void *) scheduler;
swim_ev_init(&scheduler->input, swim_scheduler_on_input);
scheduler->input.data = (void *) scheduler;
rlist_create(&scheduler->queue_output);
scheduler->on_input = on_input;
swim_transport_create(&scheduler->transport);
scheduler->codec = NULL;
int rc = swim_scheduler_set_codec(scheduler, CRYPTO_ALGO_NONE,
CRYPTO_MODE_ECB, NULL, 0);
assert(rc == 0);
(void) rc;
}

int
Expand Down Expand Up @@ -338,6 +327,42 @@ swim_scheduler_destroy(struct swim_scheduler *scheduler)
swim_scheduler_stop_input(scheduler);
}

/**
* Encrypt data and prepend it with a fresh crypto algorithm's
* initial vector.
*/
static inline int
swim_encrypt(struct crypto_codec *c, const char *in, int in_size,
char *out, int out_size)
{
assert(out_size >= crypto_codec_iv_size(c));
int iv_size = crypto_codec_gen_iv(c, out, out_size);
char *iv = out;
out += iv_size;
out_size -= iv_size;
int rc = crypto_codec_encrypt(c, iv, in, in_size, out, out_size);
if (rc < 0)
return -1;
return rc + iv_size;
}

/** Decrypt data prepended with an initial vector. */
static inline int
swim_decrypt(struct crypto_codec *c, const char *in, int in_size,
char *out, int out_size)
{
int iv_size = crypto_codec_iv_size(c);
if (in_size < iv_size) {
diag_set(SwimError, "too small message, can't extract IV for "\
"decryption");
return -1;
}
const char *iv = in;
in += iv_size;
in_size -= iv_size;
return crypto_codec_decrypt(c, iv, in, in_size, out, out_size);
}

/**
* Begin packet transmission. Prepare a next task in the queue to
* send its packet: build a meta header, pop the task from the
Expand Down Expand Up @@ -411,8 +436,34 @@ swim_complete_send(struct swim_scheduler *scheduler, struct swim_task *task,
task->complete(task, scheduler, size);
}

/**
* On a new EV_WRITE event send a next packet from the queue
* encrypted with the currently chosen algorithm.
*/
static void
swim_on_encrypted_output(struct ev_loop *loop, struct ev_io *io, int events)
{
struct swim_scheduler *scheduler = (struct swim_scheduler *) io->data;
const struct sockaddr_in *dst;
struct swim_task *task = swim_begin_send(scheduler, loop, io, events,
&dst);
if (task == NULL)
return;
char *buf = static_alloc(UDP_PACKET_SIZE);
assert(buf != NULL);
ssize_t size = swim_encrypt(scheduler->codec, task->packet.buf,
task->packet.pos - task->packet.buf,
buf, UDP_PACKET_SIZE);
if (size > 0)
size = swim_do_send(scheduler, buf, size, dst);
swim_complete_send(scheduler, task, size);
}

/**
* On a new EV_WRITE event send a next packet without encryption.
*/
static void
swim_scheduler_on_output(struct ev_loop *loop, struct ev_io *io, int events)
swim_on_plain_output(struct ev_loop *loop, struct ev_io *io, int events)
{
struct swim_scheduler *scheduler = (struct swim_scheduler *) io->data;
const struct sockaddr_in *dst;
Expand Down Expand Up @@ -515,8 +566,35 @@ swim_complete_recv(struct swim_scheduler *scheduler, const char *buf,
diag_log();
}

/**
* On a new EV_READ event receive an encrypted packet from the
* network.
*/
static void
swim_on_encrypted_input(struct ev_loop *loop, struct ev_io *io, int events)
{
struct swim_scheduler *scheduler = (struct swim_scheduler *) io->data;
/*
* Buffer for decrypted data is on stack, not on static
* memory, because the SWIM code uses static memory as
* well and can accidentally rewrite the packet data.
*/
char buf[UDP_PACKET_SIZE];
swim_begin_recv(scheduler, loop, io, events);

char *ibuf = static_alloc(UDP_PACKET_SIZE);
assert(ibuf != NULL);
ssize_t size = swim_do_recv(scheduler, ibuf, UDP_PACKET_SIZE);
if (size > 0) {
size = swim_decrypt(scheduler->codec, ibuf, size,
buf, UDP_PACKET_SIZE);
}
swim_complete_recv(scheduler, buf, size);
}

/** On a new EV_READ event receive a packet from the network. */
static void
swim_scheduler_on_input(struct ev_loop *loop, struct ev_io *io, int events)
swim_on_plain_input(struct ev_loop *loop, struct ev_io *io, int events)
{
struct swim_scheduler *scheduler = (struct swim_scheduler *) io->data;
char buf[UDP_PACKET_SIZE];
Expand All @@ -525,6 +603,31 @@ swim_scheduler_on_input(struct ev_loop *loop, struct ev_io *io, int events)
swim_complete_recv(scheduler, buf, size);
}

int
swim_scheduler_set_codec(struct swim_scheduler *scheduler,
enum crypto_algo algo, enum crypto_mode mode,
const char *key, int key_size)
{
if (algo == CRYPTO_ALGO_NONE) {
if (scheduler->codec != NULL) {
crypto_codec_delete(scheduler->codec);
scheduler->codec = NULL;
}
swim_ev_set_cb(&scheduler->output, swim_on_plain_output);
swim_ev_set_cb(&scheduler->input, swim_on_plain_input);
return 0;
}
struct crypto_codec *newc = crypto_codec_new(algo, mode, key, key_size);
if (newc == NULL)
return -1;
if (scheduler->codec != NULL)
crypto_codec_delete(scheduler->codec);
scheduler->codec = newc;
swim_ev_set_cb(&scheduler->output, swim_on_encrypted_output);
swim_ev_set_cb(&scheduler->input, swim_on_encrypted_input);
return 0;
}

const char *
swim_inaddr_str(const struct sockaddr_in *addr)
{
Expand Down
27 changes: 25 additions & 2 deletions src/lib/swim/swim_io.h
Expand Up @@ -33,6 +33,7 @@
#include "tt_static.h"
#include "small/rlist.h"
#include "salad/stailq.h"
#include "crypto/crypto.h"
#include "swim_transport.h"
#include "tarantool_ev.h"
#include "uuid/tt_uuid.h"
Expand All @@ -57,13 +58,24 @@ enum {
* configuration.
*/
UDP_PACKET_SIZE = 1472,
/**
* Data can be encrypted, which usually makes it slightly
* bigger in size. Also, to decode data the receiver needs
* two keys: private key and public initial vector. Public
* initial vector is generated randomly for each packet
* and prepends the data. This is why maximal data size is
* reduced by one block and IV sizes.
*/
MAX_PACKET_SIZE = UDP_PACKET_SIZE - CRYPTO_MAX_BLOCK_SIZE -
CRYPTO_MAX_IV_SIZE,

};

/**
* UDP packet. Works as an allocator, allowing to fill its body
* gradually, while preserving prefix for metadata.
*
* < - - - -UDP_PACKET_SIZE- - - - ->
* < - - - -MAX_PACKET_SIZE- - - - ->
* +--------+-----------------------+
* | meta | body | *free* |
* +--------+-----------------------+
Expand All @@ -86,7 +98,7 @@ struct swim_packet {
*/
char meta[0];
/** Packet body buffer. */
char buf[UDP_PACKET_SIZE];
char buf[MAX_PACKET_SIZE];
/**
* Pointer to the end of the buffer. Just sugar to do not
* write 'buf + sizeof(buf)' each time.
Expand Down Expand Up @@ -147,6 +159,11 @@ typedef void (*swim_scheduler_on_input_f)(struct swim_scheduler *scheduler,
struct swim_scheduler {
/** Transport to send/receive packets. */
struct swim_transport transport;
/**
* Codec to encode messages before sending, and decode
* before lifting up to the SWIM core logic.
*/
struct crypto_codec *codec;
/**
* Function called when a packet is received. It takes
* packet body, while meta is handled by transport level
Expand Down Expand Up @@ -180,6 +197,12 @@ int
swim_scheduler_bind(struct swim_scheduler *scheduler,
const struct sockaddr_in *addr);

/** Set a new codec to encrypt/decrypt messages. */
int
swim_scheduler_set_codec(struct swim_scheduler *scheduler,
enum crypto_algo algo, enum crypto_mode mode,
const char *key, int key_size);

/** Stop accepting new packets from the network. */
void
swim_scheduler_stop_input(struct swim_scheduler *scheduler);
Expand Down
5 changes: 5 additions & 0 deletions src/lib/swim/swim_proto.h
Expand Up @@ -47,6 +47,11 @@ enum {
* SWIM binary protocol structures and helpers. Below is a picture
* of a SWIM message template:
*
* +-----------------Public data, not encrypted------------------+
* | |
* | Initial vector, size depends on chosen algorithm. |
* | Next data is encrypted. |
* | |
* +----------Meta section, handled by transport level-----------+
* | { |
* | SWIM_META_TARANTOOL_VERSION: uint, Tarantool version ID,|
Expand Down

0 comments on commit 6137c19

Please sign in to comment.