diff --git a/src/lib/swim/CMakeLists.txt b/src/lib/swim/CMakeLists.txt index 8ba99d80384c7ad30183be8be338fe57fe7899b9..11202dce38e995a0480caa6d3b25ba5a6191e436 100644 --- a/src/lib/swim/CMakeLists.txt +++ b/src/lib/swim/CMakeLists.txt @@ -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}) diff --git a/src/lib/swim/swim.c b/src/lib/swim/swim.c index 9d67c4cb8866b795b53c8d19f66e3fbb4004834a..1283520d3bd78947572cdb0c6437dfebf14ce4d4 100644 --- a/src/lib/swim/swim.c +++ b/src/lib/swim/swim.c @@ -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) { diff --git a/src/lib/swim/swim.h b/src/lib/swim/swim.h index 5f3134cc4c1f3066e5c18f9fd3403adeae02b65e..6db12ba9e4c49628e06e9cd42d770d1987336080 100644 --- a/src/lib/swim/swim.h +++ b/src/lib/swim/swim.h @@ -32,6 +32,7 @@ */ #include <stdbool.h> #include <stdint.h> +#include "crypto/crypto.h" #include "swim_constants.h" #if defined(__cplusplus) @@ -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. diff --git a/src/lib/swim/swim_ev.h b/src/lib/swim/swim_ev.h index fe261ff38bfe09a983a6d8d0d0790e1d3893a504..1bd81306f39530f8e72364c6b2db9e3eac0d2110 100644 --- a/src/lib/swim/swim_ev.h +++ b/src/lib/swim/swim_ev.h @@ -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 */ diff --git a/src/lib/swim/swim_io.c b/src/lib/swim/swim_io.c index 39aaaf83c87ea41d8e4ed80229fd85601fd64568..c55c276cb7b931577c7eecdc3c87319d3dacb01d 100644 --- a/src/lib/swim/swim_io.c +++ b/src/lib/swim/swim_io.c @@ -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 @@ -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 @@ -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; @@ -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]; @@ -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) { diff --git a/src/lib/swim/swim_io.h b/src/lib/swim/swim_io.h index 42d94fc92ba1ae9320f98746b460a4b263715f27..6bf42cb05a64c78e6361d371c2688650445c9e7f 100644 --- a/src/lib/swim/swim_io.h +++ b/src/lib/swim/swim_io.h @@ -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" @@ -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* | * +--------+-----------------------+ @@ -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. @@ -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 @@ -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); diff --git a/src/lib/swim/swim_proto.h b/src/lib/swim/swim_proto.h index beb9bb1fed71e8345aed1f9a7bad4e69fa6f49aa..482d79fb19cf072e141a86829c3bd51f8c07972d 100644 --- a/src/lib/swim/swim_proto.h +++ b/src/lib/swim/swim_proto.h @@ -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,| diff --git a/test/unit/swim.c b/test/unit/swim.c index c6ef1eebc0a67976adcb396cd1cd9224f7f71278..6467aa35ece36c303722982cda7759911e857388 100644 --- a/test/unit/swim.c +++ b/test/unit/swim.c @@ -31,6 +31,7 @@ #include "memory.h" #include "fiber.h" +#include "random.h" #include "uuid/tt_uuid.h" #include "unit.h" #include "uri/uri.h" @@ -888,10 +889,54 @@ swim_test_indirect_ping(void) swim_finish_test(); } +static void +swim_test_encryption(void) +{ + swim_start_test(3); + struct swim_cluster *cluster = swim_cluster_new(2); + const char *key = "1234567812345678"; + swim_cluster_set_codec(cluster, CRYPTO_ALGO_AES128, CRYPTO_MODE_CBC, + key, CRYPTO_AES128_KEY_SIZE); + swim_cluster_add_link(cluster, 0, 1); + + is(swim_cluster_wait_fullmesh(cluster, 2), 0, + "cluster works with encryption"); + swim_cluster_delete(cluster); + /* + * Test that the instances can not interact with different + * encryption keys. + */ + cluster = swim_cluster_new(2); + struct swim *s1 = swim_cluster_member(cluster, 0); + int rc = swim_set_codec(s1, CRYPTO_ALGO_AES128, CRYPTO_MODE_CBC, + key, CRYPTO_AES128_KEY_SIZE); + fail_if(rc != 0); + struct swim *s2 = swim_cluster_member(cluster, 1); + key = "8765432187654321"; + rc = swim_set_codec(s2, CRYPTO_ALGO_AES128, CRYPTO_MODE_CBC, + key, CRYPTO_AES128_KEY_SIZE); + fail_if(rc != 0); + swim_cluster_add_link(cluster, 0, 1); + swim_run_for(2); + ok(! swim_cluster_is_fullmesh(cluster), + "different encryption keys - can't interact"); + + rc = swim_set_codec(s1, CRYPTO_ALGO_NONE, CRYPTO_MODE_ECB, NULL, 0); + fail_if(rc != 0); + rc = swim_set_codec(s2, CRYPTO_ALGO_NONE, CRYPTO_MODE_ECB, NULL, 0); + fail_if(rc != 0); + is(swim_cluster_wait_fullmesh(cluster, 2), 0, + "cluster works after encryption has been disabled"); + + swim_cluster_delete(cluster); + + swim_finish_test(); +} + static int main_f(va_list ap) { - swim_start_test(18); + swim_start_test(19); (void) ap; swim_test_ev_init(); @@ -915,6 +960,7 @@ main_f(va_list ap) swim_test_payload_basic(); swim_test_payload_refutation(); swim_test_indirect_ping(); + swim_test_encryption(); swim_test_transport_free(); swim_test_ev_free(); @@ -927,6 +973,7 @@ main_f(va_list ap) int main() { + random_init(); time_t seed = time(NULL); srand(seed); memory_init(); @@ -951,6 +998,7 @@ main() say_logger_free(); fiber_free(); memory_free(); + random_free(); return test_result; } \ No newline at end of file diff --git a/test/unit/swim.result b/test/unit/swim.result index 587f66c7a976ba11a03220fbe1acd23f4b2312ee..4093ecb931e133ff6472a10041c0b59d5760eb3a 100644 --- a/test/unit/swim.result +++ b/test/unit/swim.result @@ -1,5 +1,5 @@ *** main_f *** -1..18 +1..19 *** swim_test_one_link *** 1..6 ok 1 - no rounds - no fullmesh @@ -188,4 +188,11 @@ ok 17 - subtests ok 2 - as well as S2 - they communicated via S3 ok 18 - subtests *** swim_test_indirect_ping: done *** + *** swim_test_encryption *** + 1..3 + ok 1 - cluster works with encryption + ok 2 - different encryption keys - can't interact + ok 3 - cluster works after encryption has been disabled +ok 19 - subtests + *** swim_test_encryption: done *** *** main_f: done *** diff --git a/test/unit/swim_test_utils.c b/test/unit/swim_test_utils.c index f55388055cb44efd0b508c43acbdf7bb13b78430..ffd42cbd0b1b45d059488783373c58e935022451 100644 --- a/test/unit/swim_test_utils.c +++ b/test/unit/swim_test_utils.c @@ -227,9 +227,9 @@ swim_cluster_new(int size) return res; } -#define swim_cluster_set_cfg(cluster, ...) ({ \ +#define swim_cluster_set_cfg(cluster, func, ...) ({ \ for (int i = 0; i < cluster->size; ++i) { \ - int rc = swim_cfg(cluster->node[i].swim, __VA_ARGS__); \ + int rc = func(cluster->node[i].swim, __VA_ARGS__); \ assert(rc == 0); \ (void) rc; \ } \ @@ -238,14 +238,22 @@ swim_cluster_new(int size) void swim_cluster_set_ack_timeout(struct swim_cluster *cluster, double ack_timeout) { - swim_cluster_set_cfg(cluster, NULL, -1, ack_timeout, -1, NULL); + swim_cluster_set_cfg(cluster, swim_cfg, NULL, -1, ack_timeout, -1, NULL); cluster->ack_timeout = ack_timeout; } +void +swim_cluster_set_codec(struct swim_cluster *cluster, enum crypto_algo algo, + enum crypto_mode mode, const char *key, int key_size) +{ + swim_cluster_set_cfg(cluster, swim_set_codec, algo, mode, + key, key_size); +} + void swim_cluster_set_gc(struct swim_cluster *cluster, enum swim_gc_mode gc_mode) { - swim_cluster_set_cfg(cluster, NULL, -1, -1, gc_mode, NULL); + swim_cluster_set_cfg(cluster, swim_cfg, NULL, -1, -1, gc_mode, NULL); cluster->gc_mode = gc_mode; } diff --git a/test/unit/swim_test_utils.h b/test/unit/swim_test_utils.h index b786dfd7900fa4d49bc6db5c810c37a42e948941..0e6f29d80f2af7f874fc84c78f60b2451e8b8dbe 100644 --- a/test/unit/swim_test_utils.h +++ b/test/unit/swim_test_utils.h @@ -48,6 +48,14 @@ swim_cluster_new(int size); void swim_cluster_set_ack_timeout(struct swim_cluster *cluster, double ack_timeout); +/** + * Set an encryption algorithm and a key for each instance in + * @a cluster. + */ +void +swim_cluster_set_codec(struct swim_cluster *cluster, enum crypto_algo algo, + enum crypto_mode mode, const char *key, int key_size); + /** * Change number of unacknowledged pings to delete a dead member * of all the instances in the cluster.