[PATCH] lib: Add per-message encryption with OpenSSL

  • From: Dimitri Staessens <dimitri@ouroboros.rocks>
  • To: ouroboros@xxxxxxxxxxxxx
  • Date: Fri, 2 Aug 2019 19:12:34 +0200

This adds a per-message symmetric encryption using the OpenSSL
library. At flow allocation, an Elliptic Curve Diffie-Hellman exchange
is performed to derive a shared secret, which is then hashed using
SHA3-256 to be used as a key for symmetric AES-256 encryption. Each
message on an encrypted flow adds a small crypto header that includes
a random 128-bit Initialization Vector (IV). If the server does not
have OpenSSL enabled, the flow allocation will fail with an -ECRYPT
error.

Future optimizations are to piggyback the public keys on the flow
allocation message, and to enable per-flow encryption that maintains
the context of the encryption over multiple packets and doesn't
require sending IVs.

Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
---
 doc/man/flow_alloc.3           |   3 +
 include/ouroboros/errno.h      |   9 +-
 include/ouroboros/qos.h        |  74 ++++-
 include/ouroboros/sockets.h.in |   4 +-
 src/ipcpd/eth/eth.c            |   3 +
 src/ipcpd/udp/main.c           |   4 +
 src/ipcpd/unicast/fa.c         |   3 +
 src/lib/config.h.in            |   4 +
 src/lib/crypt.c                | 490 +++++++++++++++++++++++++++++++++
 src/lib/dev.c                  | 115 +++++++-
 src/lib/qosspec.proto          |   1 +
 src/lib/sockets.c              |   6 +-
 12 files changed, 690 insertions(+), 26 deletions(-)
 create mode 100644 src/lib/crypt.c

diff --git a/doc/man/flow_alloc.3 b/doc/man/flow_alloc.3
index f4a6cf8..48923fd 100644
--- a/doc/man/flow_alloc.3
+++ b/doc/man/flow_alloc.3
@@ -94,6 +94,9 @@ Not enough system memory resources available to allocate the 
flow.
 .B -ETIMEDOUT
 Flow allocation timed out.
 
+.B -ECRYPT
+The requested encryption is not supported.
+
 .SH ATTRIBUTES
 
 For an explanation of the terms used in this section, see \fBattributes\fR(7).
diff --git a/include/ouroboros/errno.h b/include/ouroboros/errno.h
index 27b21e8..2004975 100644
--- a/include/ouroboros/errno.h
+++ b/include/ouroboros/errno.h
@@ -25,11 +25,12 @@
 
 #include <errno.h>
 
-#define ENOTALLOC    1000 /* Flow is not allocated */
-#define EIPCPTYPE    1001 /* Unknown IPCP type */
+#define ENOTALLOC    1000 /* Flow is not allocated           */
+#define EIPCPTYPE    1001 /* Unknown IPCP type               */
 #define EIRMD        1002 /* Failed to communicate with IRMD */
 #define EIPCP        1003 /* Failed to communicate with IPCP */
-#define EIPCPSTATE   1004 /* Target in wrong state */
-#define EFLOWDOWN    1005 /* Flow is down */
+#define EIPCPSTATE   1004 /* Target in wrong state           */
+#define EFLOWDOWN    1005 /* Flow is down                    */
+#define ECRYPT       1006 /* Encryption error                */
 
 #endif /* OUROBOROS_ERRNO_H */
diff --git a/include/ouroboros/qos.h b/include/ouroboros/qos.h
index 0e4518f..f5becaa 100644
--- a/include/ouroboros/qos.h
+++ b/include/ouroboros/qos.h
@@ -34,6 +34,7 @@ typedef struct qos_spec {
         uint32_t ber;           /* Bit error rate, errors per billion bits */
         uint8_t  in_order;      /* In-order delivery, enables FRCT */
         uint32_t max_gap;       /* In ms */
+        uint32_t cypher_s;      /* Cypher strength, 0 = no encryption */
 } qosspec_t;
 
 static const qosspec_t qos_raw = {
@@ -43,7 +44,8 @@ static const qosspec_t qos_raw = {
         .loss         = 1,
         .ber          = 1,
         .in_order     = 0,
-        .max_gap      = UINT32_MAX
+        .max_gap      = UINT32_MAX,
+        .cypher_s     = 0
 };
 
 static const qosspec_t qos_raw_no_errors = {
@@ -53,7 +55,19 @@ static const qosspec_t qos_raw_no_errors = {
         .loss         = 1,
         .ber          = 0,
         .in_order     = 0,
-        .max_gap      = UINT32_MAX
+        .max_gap      = UINT32_MAX,
+        .cypher_s     = 0
+};
+
+static const qosspec_t qos_raw_crypt = {
+        .delay        = UINT32_MAX,
+        .bandwidth    = 0,
+        .availability = 0,
+        .loss         = 1,
+        .ber          = 0,
+        .in_order     = 0,
+        .max_gap      = UINT32_MAX,
+        .cypher_s     = 256
 };
 
 static const qosspec_t qos_best_effort = {
@@ -63,7 +77,19 @@ static const qosspec_t qos_best_effort = {
         .loss         = 1,
         .ber          = 0,
         .in_order     = 1,
-        .max_gap      = UINT32_MAX
+        .max_gap      = UINT32_MAX,
+        .cypher_s     = 0
+};
+
+static const qosspec_t qos_best_effort_crypt = {
+        .delay        = UINT32_MAX,
+        .bandwidth    = 0,
+        .availability = 0,
+        .loss         = 1,
+        .ber          = 0,
+        .in_order     = 1,
+        .max_gap      = UINT32_MAX,
+        .cypher_s     = 256
 };
 
 static const qosspec_t qos_video   = {
@@ -73,7 +99,19 @@ static const qosspec_t qos_video   = {
         .loss         = 1,
         .ber          = 0,
         .in_order     = 1,
-        .max_gap      = 100
+        .max_gap      = 100,
+        .cypher_s     = 0
+};
+
+static const qosspec_t qos_video_crypt   = {
+        .delay        = 100,
+        .bandwidth    = UINT64_MAX,
+        .availability = 3,
+        .loss         = 1,
+        .ber          = 0,
+        .in_order     = 1,
+        .max_gap      = 100,
+        .cypher_s     = 256
 };
 
 static const qosspec_t qos_voice = {
@@ -83,7 +121,19 @@ static const qosspec_t qos_voice = {
         .loss         = 1,
         .ber          = 0,
         .in_order     = 1,
-        .max_gap      = 50
+        .max_gap      = 50,
+        .cypher_s     = 0
+};
+
+static const qosspec_t qos_voice_crypt = {
+        .delay        = 50,
+        .bandwidth    = 100000,
+        .availability = 5,
+        .loss         = 1,
+        .ber          = 0,
+        .in_order     = 1,
+        .max_gap      = 50,
+        .cypher_s     = 256
 };
 
 static const qosspec_t qos_data = {
@@ -93,7 +143,19 @@ static const qosspec_t qos_data = {
         .loss         = 0,
         .ber          = 0,
         .in_order     = 1,
-        .max_gap      = 2000
+        .max_gap      = 2000,
+        .cypher_s     = 0
+};
+
+static const qosspec_t qos_data_crypt = {
+        .delay        = 1000,
+        .bandwidth    = 0,
+        .availability = 0,
+        .loss         = 0,
+        .ber          = 0,
+        .in_order     = 1,
+        .max_gap      = 2000,
+        .cypher_s     = 256
 };
 
 #endif /* OUROBOROS_QOS_H */
diff --git a/include/ouroboros/sockets.h.in b/include/ouroboros/sockets.h.in
index 1e9dc9c..4f03ca4 100644
--- a/include/ouroboros/sockets.h.in
+++ b/include/ouroboros/sockets.h.in
@@ -60,8 +60,8 @@ irm_msg_t * send_recv_irm_msg(irm_msg_t * msg);
 
 
 /* qos message conversion needed in different components */
-qosspec_msg_t spec_to_msg(qosspec_t * qs);
+qosspec_msg_t spec_to_msg(const qosspec_t * qs);
 
-qosspec_t     msg_to_spec(qosspec_msg_t * msg);
+qosspec_t     msg_to_spec(const qosspec_msg_t * msg);
 
 #endif
diff --git a/src/ipcpd/eth/eth.c b/src/ipcpd/eth/eth.c
index 1a33227..dccfd19 100644
--- a/src/ipcpd/eth/eth.c
+++ b/src/ipcpd/eth/eth.c
@@ -162,6 +162,7 @@ struct mgmt_msg {
         uint32_t ber;
         uint32_t max_gap;
         uint32_t delay;
+        uint16_t cypher_s;
         uint8_t  in_order;
 #if defined (BUILD_ETH_DIX)
         uint8_t  code;
@@ -485,6 +486,7 @@ static int eth_ipcp_alloc(const uint8_t * dst_addr,
         msg->ber          = hton32(qs.ber);
         msg->in_order     = qs.in_order;
         msg->max_gap      = hton32(qs.max_gap);
+        msg->cypher_s     = hton16(qs.cypher_s);
 
         memcpy(msg + 1, hash, ipcp_dir_hash_len());
 
@@ -731,6 +733,7 @@ static int eth_ipcp_mgmt_frame(const uint8_t * buf,
                 qs.ber = ntoh32(msg->ber);
                 qs.in_order = msg->in_order;
                 qs.max_gap = ntoh32(msg->max_gap);
+                qs.cypher_s = hton32(msg->cypher_s);
 
                 if (shim_data_reg_has(eth_data.shim_data,
                                       buf + sizeof(*msg))) {
diff --git a/src/ipcpd/udp/main.c b/src/ipcpd/udp/main.c
index a1dcb60..f6aa57d 100644
--- a/src/ipcpd/udp/main.c
+++ b/src/ipcpd/udp/main.c
@@ -91,6 +91,7 @@ struct mgmt_msg {
         uint32_t loss;
         uint32_t ber;
         uint32_t max_gap;
+        uint32_t cypher_s;
 } __attribute__((packed));
 
 struct mgmt_frame {
@@ -209,6 +210,7 @@ static int ipcp_udp_port_alloc(int             skfd,
         msg->ber          = hton32(qs.ber);
         msg->in_order     = qs.in_order;
         msg->max_gap      = hton32(qs.max_gap);
+        msg->cypher_s     = hton32(qs.cypher_s);
 
         memcpy(msg + 1, dst, ipcp_dir_hash_len());
 
@@ -383,6 +385,8 @@ static int ipcp_udp_mgmt_frame(const uint8_t *    buf,
                 qs.ber          = ntoh32(msg->ber);
                 qs.in_order     = msg->in_order;
                 qs.max_gap      = ntoh32(msg->max_gap);
+                qs.cypher_s     = ntoh16(msg->cypher_s);
+
                 return ipcp_udp_port_req(&c_saddr, ntoh32(msg->s_eid),
                                          (uint8_t *) (msg + 1), qs);
         case FLOW_REPLY:
diff --git a/src/ipcpd/unicast/fa.c b/src/ipcpd/unicast/fa.c
index fbcbc6f..c1cb065 100644
--- a/src/ipcpd/unicast/fa.c
+++ b/src/ipcpd/unicast/fa.c
@@ -66,6 +66,7 @@ struct fa_msg {
         uint32_t loss;
         uint32_t ber;
         uint32_t max_gap;
+        uint16_t cypher_s;
 } __attribute__((packed));
 
 struct cmd {
@@ -217,6 +218,7 @@ static void * fa_handle_packet(void * o)
                         qs.ber          = ntoh32(msg->ber);
                         qs.in_order     = msg->in_order;
                         qs.max_gap      = ntoh32(msg->max_gap);
+                        qs.cypher_s     = ntoh16(msg->cypher_s);
 
                         fd = ipcp_flow_req_arr((uint8_t *) (msg + 1),
                                                ipcp_dir_hash_len(),
@@ -386,6 +388,7 @@ int fa_alloc(int             fd,
         msg->ber          = hton32(qs.ber);
         msg->in_order     = qs.in_order;
         msg->max_gap      = hton32(qs.max_gap);
+        msg->cypher_s     = hton16(qs.cypher_s);
 
         memcpy(msg + 1, dst, ipcp_dir_hash_len());
 
diff --git a/src/lib/config.h.in b/src/lib/config.h.in
index 3e5a7b1..70261ca 100644
--- a/src/lib/config.h.in
+++ b/src/lib/config.h.in
@@ -24,6 +24,10 @@
 #cmakedefine HAVE_LIBGCRYPT
 #cmakedefine HAVE_OPENSSL
 
+#ifdef HAVE_OPENSSL
+#define HAVE_ENCRYPTION
+#endif
+
 #define SYS_MAX_FLOWS       @SYS_MAX_FLOWS@
 
 #cmakedefine                SHM_RBUFF_LOCKLESS
diff --git a/src/lib/crypt.c b/src/lib/crypt.c
new file mode 100644
index 0000000..61d8b96
--- /dev/null
+++ b/src/lib/crypt.c
@@ -0,0 +1,490 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2019
+ *
+ * Elliptic curve Diffie-Hellman key exchange and
+ * AES encryption for flows using OpenSSL
+ *
+ *    Dimitri Staessens <dimitri.staessens@xxxxxxxx>
+ *    Sander Vrijders   <sander.vrijders@xxxxxxxx>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#ifdef HAVE_OPENSSL
+
+#include <openssl/evp.h>
+#include <openssl/ec.h>
+#include <openssl/pem.h>
+
+#define MSGBUFSZ 2048
+#define IVSZ     16
+#define DH_TIMEO 2 /* seconds */
+/* SYMMKEYSZ defined in dev.c */
+
+/*
+ * Derive the common secret from
+ *  your public key pair (kp)
+ *  the remote public key (pub).
+ * Store it in a preallocated buffer (s).
+ */
+static int __openssl_ecdh_derive_secret(EVP_PKEY * kp,
+                                        EVP_PKEY * pub,
+                                        uint8_t *  s)
+{
+        EVP_PKEY_CTX * ctx;
+        int            ret;
+        uint8_t *      secret;
+        size_t         secret_len;
+
+        ctx = EVP_PKEY_CTX_new(kp, NULL);
+        if (ctx == NULL)
+                goto fail_new;
+
+        ret = EVP_PKEY_derive_init(ctx);
+        if (ret != 1)
+                goto fail_ctx;
+
+        ret = EVP_PKEY_derive_set_peer(ctx, pub);
+        if (ret != 1)
+                goto fail_ctx;
+
+        ret = EVP_PKEY_derive(ctx, NULL, &secret_len);
+        if (ret != 1)
+                goto fail_ctx;
+
+        if (secret_len < SYMMKEYSZ)
+                goto fail_ctx;
+
+        secret = OPENSSL_malloc(secret_len);
+        if (secret == NULL)
+                goto fail_ctx;
+
+        ret = EVP_PKEY_derive(ctx, secret, &secret_len);
+        if (ret != 1)
+                goto fail_derive;
+
+        /* Hash the secret for use as AES key. */
+        mem_hash(HASH_SHA3_256, s, secret, secret_len);
+
+        OPENSSL_free(secret);
+        EVP_PKEY_CTX_free(ctx);
+
+        return 0;
+
+ fail_derive:
+        OPENSSL_free(s);
+ fail_ctx:
+        EVP_PKEY_CTX_free(ctx);
+ fail_new:
+        return -ECRYPT;
+}
+
+static int __openssl_ecdh_gen_key(EVP_PKEY ** kp)
+{
+        EVP_PKEY_CTX * ctx    = NULL;
+        EVP_PKEY_CTX * kctx   = NULL;
+        EVP_PKEY *     params = NULL;
+        int            ret;
+
+        ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
+        if (ctx == NULL)
+                goto fail_new_id;
+
+        ret = EVP_PKEY_paramgen_init(ctx);
+        if (ret != 1)
+                goto fail_paramgen;
+
+        ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, 
NID_X9_62_prime256v1);
+        if (ret != 1)
+                goto fail_paramgen;
+
+        ret = EVP_PKEY_paramgen(ctx, &params);
+        if (ret != 1)
+                goto fail_paramgen;
+
+        kctx = EVP_PKEY_CTX_new(params, NULL);
+        if (kctx == NULL)
+                goto fail_keygen_init;
+
+        ret = EVP_PKEY_keygen_init(kctx);
+        if (ret != 1)
+                goto fail_keygen;
+
+        ret = EVP_PKEY_keygen(kctx, kp);
+        if (ret != 1)
+                goto fail_keygen;
+
+        EVP_PKEY_free(params);
+        EVP_PKEY_CTX_free(kctx);
+        EVP_PKEY_CTX_free(ctx);
+
+        return 0;
+
+ fail_keygen:
+        EVP_PKEY_CTX_free(kctx);
+ fail_keygen_init:
+        EVP_PKEY_free(params);
+ fail_paramgen:
+        EVP_PKEY_CTX_free(ctx);
+ fail_new_id:
+        return -ECRYPT;
+}
+
+/* ECDH from the server side. */
+static int openssl_ecdh_srv(int       fd,
+                            uint8_t * s)
+{
+        EVP_PKEY *      kp    = NULL;
+        EVP_PKEY *      pub   = NULL;
+        uint8_t         buf[MSGBUFSZ];
+        ssize_t         len;
+        int             buf_sz;
+        uint8_t *       pos;
+        struct timespec timeo = {DH_TIMEO,0};
+
+        assert(s != NULL);
+
+        (void) fd;
+        (void) s;
+
+        if (__openssl_ecdh_gen_key(&kp) < 0)
+                goto fail_gen_key;
+
+        fccntl(fd, FLOWSRCVTIMEO, &timeo);
+
+        len = flow_read(fd, buf, MSGBUFSZ);
+        if (len < 0) {
+                fccntl(fd, FLOWSRCVTIMEO, NULL);
+                goto fail_get_key;
+        }
+
+        fccntl(fd, FLOWSRCVTIMEO, NULL);
+
+        pos = buf; /* i2d_PUBKEY increments the pointer, don't use buf! */
+        pub = d2i_PUBKEY(NULL, (const uint8_t **) &pos, (long) len);
+
+        pos = buf; /* i2d_PUBKEY increments the pointer, don't use buf! */
+        buf_sz = i2d_PUBKEY(kp, &pos);
+        if (buf_sz < 0)
+                goto fail_get_key;
+
+        if (flow_write(fd, buf, (size_t) buf_sz) < 0)
+                goto fail_get_key;
+
+        if (__openssl_ecdh_derive_secret(kp, pub, s) < 0)
+                goto fail_get_key;
+
+        EVP_PKEY_free(kp);
+        EVP_PKEY_free(pub);
+
+        return 0;
+
+ fail_get_key:
+        EVP_PKEY_free(kp);
+ fail_gen_key:
+        return -ECRYPT;
+}
+
+/* ECDH from the client side. */
+static int openssl_ecdh_clt(int       fd,
+                            uint8_t * s)
+{
+        EVP_PKEY *      kp    = NULL;
+        EVP_PKEY *      pub   = NULL;
+        uint8_t         buf[MSGBUFSZ];
+        int             buf_sz;
+        uint8_t *       pos;
+        ssize_t         len;
+        struct timespec timeo = {DH_TIMEO,0};
+
+        assert(s != NULL);
+
+        (void) fd;
+        (void) s;
+
+        if (__openssl_ecdh_gen_key(&kp) < 0)
+                goto fail_gen_key;
+
+        pos = buf; /* i2d_PUBKEY increments the pointer, don't use buf! */
+        buf_sz = i2d_PUBKEY(kp, &pos);
+        if (buf_sz < 0)
+                goto fail_get_key;
+
+        if (flow_write(fd, buf, (size_t) buf_sz) < 0)
+                goto fail_get_key;
+
+        fccntl(fd, FLOWSRCVTIMEO, &timeo);
+
+        len = flow_read(fd, buf, MSGBUFSZ);
+        if (len < 0) {
+                fccntl(fd, FLOWSRCVTIMEO, NULL);
+                goto fail_get_key;
+        }
+
+        fccntl(fd, FLOWSRCVTIMEO, NULL);
+
+        pos = buf; /* i2d_PUBKEY increments the pointer, don't use buf! */
+        pub = d2i_PUBKEY(NULL, (const uint8_t **) &pos, (long) len);
+
+        if (__openssl_ecdh_derive_secret(kp, pub, s) < 0)
+                goto fail_get_key;
+
+        EVP_PKEY_free(kp);
+        EVP_PKEY_free(pub);
+
+        return 0;
+
+ fail_get_key:
+        EVP_PKEY_free(kp);
+ fail_gen_key:
+        return -ECRYPT;
+}
+
+/*
+ * AES encryption calls. If FRCT is disabled, we should generate a
+ * 128-bit random IV and append it to the packet.  If the flow is
+ * reliable, we could initialize the context once, and consider the
+ * stream a single encrypted message to avoid initializing the
+ * encryption context for each packet.
+ */
+
+static int openssl_encrypt(struct flow *        f,
+                           struct shm_du_buff * sdb)
+{
+        uint8_t * out;
+        uint8_t * in;
+        uint8_t * head;
+        uint8_t   iv[IVSZ];
+        int       in_sz;
+        int       out_sz;
+        int       tmp_sz;
+        int       ret;
+
+        in = shm_du_buff_head(sdb);
+        in_sz = shm_du_buff_tail(sdb) - in;
+
+        if (random_buffer(iv, IVSZ) < 0)
+                goto fail_iv;
+
+        out = malloc(in_sz + EVP_MAX_BLOCK_LENGTH);
+        if (out == NULL)
+                goto fail_iv;
+
+        EVP_CIPHER_CTX_reset(f->ctx);
+
+        ret = EVP_EncryptInit_ex(f->ctx,
+                                 EVP_aes_256_cbc(),
+                                 NULL,
+                                 f->key,
+                                 iv);
+        if (ret != 1)
+                goto fail_encrypt_init;
+
+        ret = EVP_EncryptUpdate(f->ctx, out, &tmp_sz, in, in_sz);
+        if (ret != 1)
+                goto fail_encrypt;
+
+        out_sz = tmp_sz;
+        ret =  EVP_EncryptFinal_ex(f->ctx, out + tmp_sz, &tmp_sz);
+        if (ret != 1)
+                goto fail_encrypt;
+
+        out_sz += tmp_sz;
+
+        EVP_CIPHER_CTX_cleanup(f->ctx);
+
+        assert(out_sz >= in_sz);
+
+        head = shm_du_buff_head_alloc(sdb, IVSZ);
+        if (head == NULL)
+                goto fail_encrypt;
+
+        if (shm_du_buff_tail_alloc(sdb, out_sz - in_sz) == NULL)
+                goto fail_tail_alloc;
+
+        memcpy(head, iv, IVSZ);
+        memcpy(in, out, out_sz);
+
+        free(out);
+
+        return 0;
+
+ fail_tail_alloc:
+        shm_du_buff_head_release(sdb, IVSZ);
+ fail_encrypt:
+        EVP_CIPHER_CTX_cleanup(f->ctx);
+ fail_encrypt_init:
+        free(out);
+ fail_iv:
+        return -ECRYPT;
+}
+
+static int openssl_decrypt(struct flow *        f,
+                           struct shm_du_buff * sdb)
+{
+        uint8_t * in;
+        uint8_t * out;
+        uint8_t   iv[IVSZ];
+        int       ret;
+        int       out_sz;
+        int       in_sz;
+        int       tmp_sz;
+
+        in = shm_du_buff_head_release(sdb, IVSZ);
+
+        memcpy(iv, in, IVSZ);
+
+        in = shm_du_buff_head(sdb);
+
+        in_sz = shm_du_buff_tail(sdb) - shm_du_buff_head(sdb);
+
+        out = malloc(in_sz);
+        if (out == NULL)
+                goto fail_malloc;
+
+        EVP_CIPHER_CTX_reset(f->ctx);
+
+        ret = EVP_DecryptInit_ex(f->ctx,
+                                 EVP_aes_256_cbc(),
+                                 NULL,
+                                 f->key,
+                                 iv);
+        if (ret != 1)
+                goto fail_decrypt_init;
+
+        ret = EVP_DecryptUpdate(f->ctx, out, &tmp_sz, in, in_sz);
+        if (ret != 1)
+                goto fail_decrypt;
+
+        out_sz = tmp_sz;
+
+        ret = EVP_DecryptFinal_ex(f->ctx, out + tmp_sz, &tmp_sz);
+        if (ret != 1)
+                goto fail_decrypt;
+
+        out_sz += tmp_sz;
+
+        assert(out_sz <= in_sz);
+
+        shm_du_buff_tail_release(sdb, in_sz - out_sz);
+
+        memcpy(in, out, out_sz);
+
+        free(out);
+
+        return 0;
+
+ fail_decrypt:
+        EVP_CIPHER_CTX_cleanup(f->ctx);
+ fail_decrypt_init:
+        free(out);
+ fail_malloc:
+        return -ECRYPT;
+
+}
+
+static int openssl_crypt_init(struct flow * f)
+{
+        f->ctx = EVP_CIPHER_CTX_new();
+        if (f->ctx == NULL)
+                goto fail_new;
+
+        return 0;
+
+ fail_new:
+        return -ECRYPT;
+}
+
+static void openssl_crypt_fini(struct flow * f)
+{
+        EVP_CIPHER_CTX_free(f->ctx);
+        f->ctx = NULL;
+}
+
+#endif /* HAVE_OPENSSL */
+
+static int crypt_dh_srv(int       fd,
+                        uint8_t * s)
+{
+#ifdef HAVE_OPENSSL
+        return openssl_ecdh_clt(fd, s);
+#else
+        (void) fd;
+
+        memset(s, 0, SYMMKEYSZ);
+
+        return -ECRYPT;
+#endif
+}
+
+static int crypt_dh_clt(int       fd,
+                        uint8_t * s)
+{
+#ifdef HAVE_OPENSSL
+        return openssl_ecdh_srv(fd, s);
+#else
+        (void) fd;
+
+        memset(s, 0, SYMMKEYSZ);
+
+        return 0;
+#endif
+}
+
+static int crypt_encrypt(struct flow *        f,
+                         struct shm_du_buff * sdb)
+{
+#ifdef HAVE_OPENSSL
+        return openssl_encrypt(f, sdb);
+#else
+        (void) f;
+        (void) sdb;
+
+        return 0;
+#endif
+}
+
+static int crypt_decrypt(struct flow *        f,
+                         struct shm_du_buff * sdb)
+{
+#ifdef HAVE_OPENSSL
+        return openssl_decrypt(f, sdb);
+#else
+        (void) f;
+        (void) sdb;
+
+        return 0;
+#endif
+}
+
+
+static int crypt_init(struct flow * f)
+{
+#ifdef HAVE_OPENSSL
+        return openssl_crypt_init(f);
+#else
+        (void) f;
+
+        return 0;
+#endif
+}
+
+static void crypt_fini(struct flow * f)
+{
+#ifdef HAVE_OPENSSL
+        openssl_crypt_fini(f);
+#else
+        (void) f;
+#endif
+}
diff --git a/src/lib/dev.c b/src/lib/dev.c
index 229a147..d4bd0a6 100644
--- a/src/lib/dev.c
+++ b/src/lib/dev.c
@@ -29,6 +29,7 @@
 #include "config.h"
 
 #include <ouroboros/hash.h>
+#include <ouroboros/cacep.h>
 #include <ouroboros/errno.h>
 #include <ouroboros/dev.h>
 #include <ouroboros/ipcp-dev.h>
@@ -59,6 +60,8 @@
 #define DONE_PART    -2
 
 #define CRCLEN    (sizeof(uint32_t))
+#define SECMEMSZ  16384
+#define SYMMKEYSZ  32
 
 struct flow_set {
         size_t   idx;
@@ -98,6 +101,9 @@ struct flow {
         qosspec_t             spec;
         ssize_t               part_idx;
 
+        void *                ctx;
+        uint8_t               key[SYMMKEYSZ];
+
         pid_t                 pid;
 
         bool                  snd_timesout;
@@ -237,6 +243,8 @@ static void flow_clear(int fd)
         ai.flows[fd].pid      = -1;
 }
 
+#include "crypt.c"
+
 static void flow_fini(int fd)
 {
         assert(fd >= 0 && fd < SYS_MAX_FLOWS);
@@ -266,6 +274,9 @@ static void flow_fini(int fd)
         if (ai.flows[fd].frcti != NULL)
                 frcti_destroy(ai.flows[fd].frcti);
 
+        if (ai.flows[fd].spec.cypher_s > 0)
+                crypt_fini(&ai.flows[fd]);
+
         flow_clear(fd);
 }
 
@@ -286,15 +297,15 @@ static int flow_init(int       flow_id,
 
         ai.flows[fd].rx_rb = shm_rbuff_open(ai.pid, flow_id);
         if (ai.flows[fd].rx_rb == NULL)
-                goto fail;
+                goto fail_rx_rb;
 
         ai.flows[fd].tx_rb = shm_rbuff_open(pid, flow_id);
         if (ai.flows[fd].tx_rb == NULL)
-                goto fail;
+                goto fail_tx_rb;
 
         ai.flows[fd].set = shm_flow_set_open(pid);
         if (ai.flows[fd].set == NULL)
-                goto fail;
+                goto fail_set;
 
         ai.flows[fd].flow_id  = flow_id;
         ai.flows[fd].oflags   = FLOWFDEFAULT;
@@ -302,6 +313,9 @@ static int flow_init(int       flow_id,
         ai.flows[fd].part_idx = NO_PART;
         ai.flows[fd].spec     = qs;
 
+        if (qs.cypher_s > 0 && crypt_init(&ai.flows[fd]) < 0)
+                goto fail_crypt;
+
         ai.ports[flow_id].fd = fd;
 
         port_set_state(&ai.ports[flow_id], PORT_ID_ASSIGNED);
@@ -310,8 +324,14 @@ static int flow_init(int       flow_id,
 
         return fd;
 
- fail:
-        flow_fini(fd);
+ fail_crypt:
+        shm_flow_set_close(ai.flows[fd].set);
+ fail_set:
+        shm_rbuff_close(ai.flows[fd].tx_rb);
+ fail_tx_rb:
+        shm_rbuff_close(ai.flows[fd].rx_rb);
+ fail_rx_rb:
+        bmp_release(ai.fds, fd);
  fail_fds:
         pthread_rwlock_unlock(&ai.lock);
         return err;
@@ -347,7 +367,6 @@ static void init(int     argc,
         if (!gcry_control (GCRYCTL_INITIALIZATION_FINISHED_P)) {
                 if (!gcry_check_version(GCRYPT_VERSION))
                         goto fail_fds;
-                /* Needs to be enabled when we add encryption. */
                 gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
                 gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
         }
@@ -550,8 +569,8 @@ int flow_accept(qosspec_t *             qs,
         if (ai.flows[fd].spec.in_order != 0) {
                 ai.flows[fd].frcti = frcti_create(fd);
                 if (ai.flows[fd].frcti == NULL) {
-                        flow_fini(fd);
                         pthread_rwlock_unlock(&ai.lock);
+                        flow_dealloc(fd);
                         return -ENOMEM;
                 }
         }
@@ -561,6 +580,31 @@ int flow_accept(qosspec_t *             qs,
 
         pthread_rwlock_unlock(&ai.lock);
 
+        /* TODO: piggyback public keys at flow allocation. */
+        if (ai.flows[fd].spec.cypher_s > 0) {
+                uint8_t  key[SYMMKEYSZ];
+                uint16_t tmp;
+
+                pthread_rwlock_wrlock(&ai.lock);
+
+                tmp = ai.flows[fd].spec.cypher_s;
+                ai.flows[fd].spec.cypher_s = 0;
+
+                pthread_rwlock_unlock(&ai.lock);
+
+                if (crypt_dh_srv(fd, key) < 0) {
+                        flow_dealloc(fd);
+                        return -ECRYPT;
+                }
+
+                pthread_rwlock_wrlock(&ai.lock);
+
+                ai.flows[fd].spec.cypher_s = tmp;
+                memcpy(ai.flows[fd].key, key, SYMMKEYSZ);
+
+                pthread_rwlock_unlock(&ai.lock);
+        }
+
         return fd;
 }
 
@@ -579,9 +623,9 @@ static int __flow_alloc(const char *            dst,
                 qs->ber = 1;
 #endif
         if (join)
-                msg.code    = IRM_MSG_CODE__IRM_FLOW_JOIN;
+                msg.code = IRM_MSG_CODE__IRM_FLOW_JOIN;
         else
-                msg.code    = IRM_MSG_CODE__IRM_FLOW_ALLOC;
+                msg.code = IRM_MSG_CODE__IRM_FLOW_ALLOC;
         msg.dst     = (char *) dst;
         msg.has_pid = true;
         msg.pid     = ai.pid;
@@ -605,7 +649,7 @@ static int __flow_alloc(const char *            dst,
         }
 
         if (recv_msg->result != 0) {
-                int res =  recv_msg->result;
+                int res = recv_msg->result;
                 irm_msg__free_unpacked(recv_msg, NULL);
                 return res;
         }
@@ -630,14 +674,39 @@ static int __flow_alloc(const char *            dst,
         if (ai.flows[fd].spec.in_order != 0) {
                 ai.flows[fd].frcti = frcti_create(fd);
                 if (ai.flows[fd].frcti == NULL) {
-                        flow_fini(fd);
                         pthread_rwlock_unlock(&ai.lock);
+                        flow_dealloc(fd);
                         return -ENOMEM;
                 }
         }
 
         pthread_rwlock_unlock(&ai.lock);
 
+        /* TODO: piggyback public keys at flow allocation. */
+        if (!join && ai.flows[fd].spec.cypher_s > 0) {
+                uint8_t  key[SYMMKEYSZ];
+                uint16_t tmp;
+
+                pthread_rwlock_wrlock(&ai.lock);
+
+                tmp = ai.flows[fd].spec.cypher_s;
+                ai.flows[fd].spec.cypher_s = 0;
+
+                pthread_rwlock_unlock(&ai.lock);
+
+                if (crypt_dh_clt(fd, key) < 0) {
+                        flow_dealloc(fd);
+                        return -ECRYPT;
+                }
+
+                pthread_rwlock_wrlock(&ai.lock);
+
+                ai.flows[fd].spec.cypher_s = tmp;
+                memcpy(ai.flows[fd].key, key, SYMMKEYSZ);
+
+                pthread_rwlock_unlock(&ai.lock);
+        }
+
         return fd;
 }
 
@@ -931,6 +1000,15 @@ ssize_t flow_write(int          fd,
                 return -ENOMEM;
         }
 
+        pthread_rwlock_wrlock(&ai.lock);
+        if (flow->spec.cypher_s > 0)
+                if (crypt_encrypt(flow, sdb) < 0) {
+                        pthread_rwlock_unlock(&ai.lock);
+                        shm_rdrbuff_remove(ai.rdrb, idx);
+                        return -ENOMEM;
+                }
+        pthread_rwlock_unlock(&ai.lock);
+
         if (flow->spec.ber == 0 && add_crc(sdb) != 0) {
                 shm_rdrbuff_remove(ai.rdrb, idx);
                 return -ENOMEM;
@@ -1009,9 +1087,22 @@ ssize_t flow_read(int    fd,
                                         shm_rbuff_read_b(rb, abstime);
                                 if (idx < 0)
                                         return idx;
+
                                 sdb = shm_rdrbuff_get(ai.rdrb, idx);
-                                if (flow->spec.ber == 0  && chk_crc(sdb) != 0)
+                                if (flow->spec.ber == 0 && chk_crc(sdb) != 0) {
+                                        shm_rdrbuff_remove(ai.rdrb, idx);
                                         continue;
+                                }
+
+                                pthread_rwlock_wrlock(&ai.lock);
+                                if (flow->spec.cypher_s > 0)
+                                        if (crypt_decrypt(flow, sdb) < 0) {
+                                                
pthread_rwlock_unlock(&ai.lock);
+                                                shm_rdrbuff_remove(ai.rdrb,
+                                                                   idx);
+                                                return -ENOMEM;
+                                        }
+                                pthread_rwlock_unlock(&ai.lock);
                         } while (frcti_rcv(flow->frcti, sdb) != 0);
                 }
         }
diff --git a/src/lib/qosspec.proto b/src/lib/qosspec.proto
index c9e5a6e..0cbcd41 100644
--- a/src/lib/qosspec.proto
+++ b/src/lib/qosspec.proto
@@ -30,4 +30,5 @@ message qosspec_msg {
         required uint32 ber          = 5; /* Bit error rate, ppb */
         required uint32 in_order     = 6; /* In-order delivery */
         required uint32 max_gap      = 7; /* In ms */
+        required uint32 cypher_s     = 8; /* Crypto strength in bits */
 };
diff --git a/src/lib/sockets.c b/src/lib/sockets.c
index b08bae8..347e924 100644
--- a/src/lib/sockets.c
+++ b/src/lib/sockets.c
@@ -166,7 +166,7 @@ char * ipcp_sock_path(pid_t pid)
         return full_name;
 }
 
-qosspec_msg_t spec_to_msg(qosspec_t * qs)
+qosspec_msg_t spec_to_msg(const qosspec_t * qs)
 {
         qosspec_t     spec;
         qosspec_msg_t msg = QOSSPEC_MSG__INIT;
@@ -180,11 +180,12 @@ qosspec_msg_t spec_to_msg(qosspec_t * qs)
         msg.ber          = spec.ber;
         msg.in_order     = spec.in_order;
         msg.max_gap      = spec.max_gap;
+        msg.cypher_s     = spec.cypher_s;
 
         return msg;
 }
 
-qosspec_t msg_to_spec(qosspec_msg_t * msg)
+qosspec_t msg_to_spec(const qosspec_msg_t * msg)
 {
         qosspec_t     spec;
 
@@ -197,6 +198,7 @@ qosspec_t msg_to_spec(qosspec_msg_t * msg)
         spec.ber          = msg->ber;
         spec.in_order     = msg->in_order;
         spec.max_gap      = msg->max_gap;
+        spec.cypher_s     = msg->cypher_s;
 
         return spec;
 }
-- 
2.22.0


Other related posts:

  • » [PATCH] lib: Add per-message encryption with OpenSSL - Dimitri Staessens