hammer2 - Implement aes_256_cbc session encryption
authorMatthew Dillon <dillon@apollo.backplane.com>
Sun, 13 May 2012 00:43:17 +0000 (17:43 -0700)
committerMatthew Dillon <dillon@apollo.backplane.com>
Sun, 13 May 2012 00:53:47 +0000 (17:53 -0700)
* The AES session key and initial iv[] are transmitted in the public key
  exchange.

* The actual AES session key and initial iv[] is the data received XOR'd
  with the data sent, so if the public key exchange is broken (even if
  the verifier succeeds), the rest of the session will die a horrible
  death.

* We use aes_256_cbc and in addition to the iv[] being adjusted by the
  data in-flight we also inject some random data in each message header
  to mix iv[] up even more than it would be normally.

* We also check the message sequence number, which is embedded in the
  random data (the raw msg header's salt field), though the iv[] should
  catch any replays.

* NOTE: Verifier is still weak, but the session key and iv[] exchange
  is very strong.

sbin/hammer2/cmd_rsa.c
sbin/hammer2/crypto.c
sbin/hammer2/hammer2.h
sbin/hammer2/msg.c
sbin/hammer2/network.h

index b403b7f..75d3893 100644 (file)
@@ -35,9 +35,6 @@
 
 #include "hammer2.h"
 
-#include <openssl/rsa.h>
-#include <openssl/pem.h>
-
 /*
  * Should be run as root.  Creates /etc/hammer2/rsa.{pub,prv} using
  * an openssl command.
index 947a0cf..480568c 100644 (file)
 
 #include "hammer2.h"
 
-#include <openssl/rsa.h>
-#include <openssl/pem.h>
-#include <openssl/err.h>
-
 /*
  * Synchronously negotiate crypto for a new session.  This must occur
  * within 10 seconds or the connection is error'd out.
@@ -92,7 +88,8 @@ hammer2_crypto_negotiate(hammer2_iocom_t *iocom)
        char realname[128];
        hammer2_handshake_t handtx;
        hammer2_handshake_t handrx;
-       char buf[sizeof(handtx)];
+       char buf1[sizeof(handtx)];
+       char buf2[sizeof(handtx)];
        char *ptr;
        char *path;
        struct stat st;
@@ -248,7 +245,7 @@ urandfail:
        if (bcmp(&handrx, &handtx, sizeof(handtx)) == 0)
                goto urandfail;                 /* read all zeros */
        close(fd);
-       ERR_load_crypto_strings();
+       /* ERR_load_crypto_strings(); openssl debugging */
 
        /*
         * Handshake with the remote.
@@ -290,20 +287,20 @@ urandfail:
                         */
                        do {
                                ++*(int *)(ptr + 4);
-                               if (RSA_private_encrypt(blksize, ptr, buf,
+                               if (RSA_private_encrypt(blksize, ptr, buf1,
                                            keys[2], RSA_NO_PADDING) < 0) {
                                        iocom->ioq_rx.error =
                                                HAMMER2_IOQ_ERROR_KEYXCHGFAIL;
                                }
-                       } while (buf[0] & 0xC0);
+                       } while (buf1[0] & 0xC0);
 
-                       if (RSA_public_encrypt(blksize, buf, ptr,
+                       if (RSA_public_encrypt(blksize, buf1, buf2,
                                            keys[0], RSA_NO_PADDING) < 0) {
                                iocom->ioq_rx.error =
                                        HAMMER2_IOQ_ERROR_KEYXCHGFAIL;
                        }
                }
-               if (write(iocom->sock_fd, ptr, blksize) != (ssize_t)blksize) {
+               if (write(iocom->sock_fd, buf2, blksize) != (ssize_t)blksize) {
                        fprintf(stderr, "WRITE ERROR\n");
                }
        }
@@ -327,11 +324,11 @@ urandfail:
                ptr -= (i & blkmask);
                i += n;
                if (keys[0] && (i & blkmask) == 0) {
-                       if (RSA_private_decrypt(blksize, ptr, buf,
+                       if (RSA_private_decrypt(blksize, ptr, buf1,
                                           keys[2], RSA_NO_PADDING) < 0)
                                iocom->ioq_rx.error =
                                                HAMMER2_IOQ_ERROR_KEYXCHGFAIL;
-                       if (RSA_public_decrypt(blksize, buf, ptr,
+                       if (RSA_public_decrypt(blksize, buf1, ptr,
                                           keys[0], RSA_NO_PADDING) < 0)
                                iocom->ioq_rx.error =
                                                HAMMER2_IOQ_ERROR_KEYXCHGFAIL;
@@ -372,9 +369,40 @@ keyxchgfail:
        if (n != 0)
                goto keyxchgfail;
 
-       if (DebugOpt) {
-               fprintf(stderr, "Remote data: %s\n", handrx.quickmsg);
+       /*
+        * Calculate the session key and initialize the iv[].
+        */
+       assert(HAMMER2_AES_KEY_SIZE * 2 == sizeof(handrx.sess));
+       for (i = 0; i < HAMMER2_AES_KEY_SIZE; ++i) {
+               iocom->sess[i] = handrx.sess[i] ^ handtx.sess[i];
+               iocom->ioq_rx.iv[i] = handrx.sess[HAMMER2_AES_KEY_SIZE + i] ^
+                                     handtx.sess[HAMMER2_AES_KEY_SIZE + i];
+               iocom->ioq_tx.iv[i] = handrx.sess[HAMMER2_AES_KEY_SIZE + i] ^
+                                     handtx.sess[HAMMER2_AES_KEY_SIZE + i];
        }
+       printf("sess: ");
+       for (i = 0; i < HAMMER2_AES_KEY_SIZE; ++i)
+               printf("%02x", (unsigned char)iocom->sess[i]);
+       printf("\n");
+       printf("iv: ");
+       for (i = 0; i < HAMMER2_AES_KEY_SIZE; ++i)
+               printf("%02x", (unsigned char)iocom->ioq_rx.iv[i]);
+       printf("\n");
+
+       EVP_CIPHER_CTX_init(&iocom->ioq_rx.ctx);
+       EVP_DecryptInit_ex(&iocom->ioq_rx.ctx, HAMMER2_AES_TYPE_EVP, NULL,
+                          iocom->sess, iocom->ioq_rx.iv);
+       EVP_CIPHER_CTX_set_padding(&iocom->ioq_rx.ctx, 0);
+
+       EVP_CIPHER_CTX_init(&iocom->ioq_tx.ctx);
+       EVP_EncryptInit_ex(&iocom->ioq_tx.ctx, HAMMER2_AES_TYPE_EVP, NULL,
+                          iocom->sess, iocom->ioq_tx.iv);
+       EVP_CIPHER_CTX_set_padding(&iocom->ioq_tx.ctx, 0);
+
+       iocom->flags |= HAMMER2_IOCOMF_CRYPTED;
+
+       if (DebugOpt)
+               fprintf(stderr, "auth success: %s\n", handrx.quickmsg);
 done:
        if (path)
                free(path);
@@ -385,3 +413,122 @@ done:
        if (keys[1])
                RSA_free(keys[2]);
 }
+
+/*
+ * Decrypt pending data in the ioq's fifo.  The data is decrypted in-place.
+ */
+void
+hammer2_crypto_decrypt(hammer2_iocom_t *iocom, hammer2_ioq_t *ioq)
+{
+       int p_len;
+       int n;
+       int i;
+       char buf[512];
+
+       if ((iocom->flags & HAMMER2_IOCOMF_CRYPTED) == 0)
+               return;
+       p_len = ioq->fifo_end - ioq->fifo_cdx;
+       p_len &= ~HAMMER2_AES_KEY_MASK;
+       if (p_len == 0)
+               return;
+       for (i = 0; i < p_len; i += n) {
+               n = (p_len - i > (int)sizeof(buf)) ?
+                       (int)sizeof(buf) : p_len - i;
+               bcopy(ioq->buf + ioq->fifo_cdx + i, buf, n);
+               EVP_DecryptUpdate(&ioq->ctx,
+                                 ioq->buf + ioq->fifo_cdx + i, &n,
+                                 buf, n);
+       }
+       ioq->fifo_cdx += p_len;
+}
+
+/*
+ * Decrypt data in the message's auxilary buffer.  The data is decrypted
+ * in-place.
+ */
+void
+hammer2_crypto_decrypt_aux(hammer2_iocom_t *iocom, hammer2_ioq_t *ioq,
+                          hammer2_msg_t *msg, int already)
+{
+       int p_len;
+       int n;
+       int i;
+       char buf[512];
+
+       if ((iocom->flags & HAMMER2_IOCOMF_CRYPTED) == 0)
+               return;
+       p_len = msg->aux_size;
+       assert((p_len & HAMMER2_AES_KEY_MASK) == 0);
+       if (p_len == 0)
+               return;
+       i = already;
+       while (i < p_len) {
+               n = (p_len - i > (int)sizeof(buf)) ?
+                       (int)sizeof(buf) : p_len - i;
+               bcopy(msg->aux_data + i, buf, n);
+               EVP_DecryptUpdate(&ioq->ctx,
+                                 msg->aux_data + i, &n,
+                                 buf, n);
+               i += n;
+       }
+#if 0
+       EVP_DecryptUpdate(&iocom->ioq_rx.ctx,
+                         msg->aux_data, &p_len,
+                         msg->aux_data, p_len);
+#endif
+}
+
+int
+hammer2_crypto_encrypt(hammer2_iocom_t *iocom, hammer2_ioq_t *ioq,
+                      struct iovec *iov, int n)
+{
+       int p_len;
+       int i;
+       int already;
+       int nmax;
+
+       if ((iocom->flags & HAMMER2_IOCOMF_CRYPTED) == 0)
+               return (n);
+       nmax = sizeof(ioq->buf) - ioq->fifo_cdx;        /* max new bytes */
+       already = ioq->fifo_cdx - ioq->fifo_beg;        /* already encrypted */
+
+       for (i = 0; i < n; ++i) {
+               p_len = iov[i].iov_len;
+               if (p_len <= already) {
+                       already -= p_len;
+                       continue;
+               }
+               p_len -= already;
+               if (p_len > nmax)
+                       p_len = nmax;
+               EVP_EncryptUpdate(&ioq->ctx,
+                                 ioq->buf + ioq->fifo_cdx, &p_len,
+                                 (char *)iov[i].iov_base + already, p_len);
+               ioq->fifo_cdx += p_len;
+               ioq->fifo_end += p_len;
+               nmax -= p_len;
+               if (nmax == 0)
+                       break;
+               already = 0;
+       }
+       iov[0].iov_base = ioq->buf + ioq->fifo_beg;
+       iov[0].iov_len = ioq->fifo_cdx - ioq->fifo_beg;
+
+       return (1);
+}
+
+void
+hammer2_crypto_encrypt_wrote(hammer2_iocom_t *iocom, hammer2_ioq_t *ioq,
+                            int nact)
+{
+       if ((iocom->flags & HAMMER2_IOCOMF_CRYPTED) == 0)
+               return;
+       if (nact == 0)
+               return;
+       ioq->fifo_beg += nact;
+       if (ioq->fifo_beg == ioq->fifo_end) {
+               ioq->fifo_beg = 0;
+               ioq->fifo_cdx = 0;
+               ioq->fifo_end = 0;
+       }
+}
index fc3fc05..72fc488 100644 (file)
@@ -125,6 +125,14 @@ void hammer2_iocom_drain(hammer2_iocom_t *iocom);
 void hammer2_iocom_flush(hammer2_iocom_t *iocom);
 
 void hammer2_crypto_negotiate(hammer2_iocom_t *iocom);
+void hammer2_crypto_decrypt(hammer2_iocom_t *iocom, hammer2_ioq_t *ioq);
+void hammer2_crypto_decrypt_aux(hammer2_iocom_t *iocom, hammer2_ioq_t *ioq,
+                       hammer2_msg_t *msg, int already);
+int hammer2_crypto_encrypt(hammer2_iocom_t *iocom, hammer2_ioq_t *ioq,
+                       struct iovec *iov, int n);
+void hammer2_crypto_encrypt_wrote(hammer2_iocom_t *iocom, hammer2_ioq_t *ioq,
+                       int nact);
+
 
 void hammer2_debug_remote(hammer2_msg_t *msg);
 void msg_printf(hammer2_msg_t *msg, const char *ctl, ...);
index 322ef77..4fc4439 100644 (file)
@@ -300,7 +300,7 @@ hammer2_ioq_read(hammer2_iocom_t *iocom)
         * allocate a msg until we have its core header.
         */
        bytes = ioq->fifo_end - ioq->fifo_beg;
-       nmax = sizeof(iocom->rxbuf) - ioq->fifo_end;
+       nmax = sizeof(ioq->buf) - ioq->fifo_end;
        msg = ioq->msg;
 
        switch(ioq->state) {
@@ -313,7 +313,7 @@ hammer2_ioq_read(hammer2_iocom_t *iocom)
                 */
                if (bytes < (int)sizeof(msg->any.head)) {
                        n = read(iocom->sock_fd,
-                                iocom->rxbuf + ioq->fifo_end,
+                                ioq->buf + ioq->fifo_end,
                                 nmax);
                        if (n <= 0) {
                                if (n == 0) {
@@ -342,12 +342,14 @@ hammer2_ioq_read(hammer2_iocom_t *iocom)
                if (bytes < (int)sizeof(msg->any.head))
                        break;
 
-               flags = 0;
-               head = (void *)(iocom->rxbuf + ioq->fifo_beg);
-
                /*
-                * XXX Decrypt the core header
+                * Calculate the header, decrypt data received so far.
+                * Data will be decrypted in-place.  Partial blocks are
+                * not immediately decrypted.
                 */
+               hammer2_crypto_decrypt(iocom, ioq);
+               flags = 0;
+               head = (void *)(ioq->buf + ioq->fifo_beg);
 
                /*
                 * Check and fixup the core header.  Note that the icrc
@@ -414,11 +416,11 @@ hammer2_ioq_read(hammer2_iocom_t *iocom)
                 * book-keeping is easier if we don't bcopy() yet.
                 */
                if (bytes + nmax < ioq->hbytes) {
-                       bcopy(iocom->rxbuf + ioq->fifo_beg, iocom->rxbuf,
-                             bytes);
+                       bcopy(ioq->buf + ioq->fifo_beg, ioq->buf, bytes);
+                       ioq->fifo_cdx -= ioq->fifo_beg;
                        ioq->fifo_beg = 0;
                        ioq->fifo_end = bytes;
-                       nmax = sizeof(iocom->rxbuf) - ioq->fifo_end;
+                       nmax = sizeof(ioq->buf) - ioq->fifo_end;
                }
                ioq->state = HAMMER2_MSGQ_STATE_HEADER2;
                /* fall through */
@@ -460,9 +462,11 @@ hammer2_ioq_read(hammer2_iocom_t *iocom)
                }
 
                /*
-                * XXX Decrypt the extended header
+                * Calculate the extended header, decrypt data received
+                * so far.
                 */
-               head = (void *)(iocom->rxbuf + ioq->fifo_beg);
+               hammer2_crypto_decrypt(iocom, ioq);
+               head = (void *)(ioq->buf + ioq->fifo_beg);
 
                /*
                 * Check the crc on the extended header
@@ -507,17 +511,24 @@ hammer2_ioq_read(hammer2_iocom_t *iocom)
                 * way so we can check the crc.
                 */
                assert(msg->aux_size == 0);
+               ioq->already = ioq->fifo_cdx - ioq->fifo_beg;
+               if (ioq->already > ioq->abytes)
+                       ioq->already = ioq->abytes;
                if (bytes >= ioq->abytes) {
-                       bcopy(iocom->rxbuf + ioq->fifo_beg, msg->aux_data,
+                       bcopy(ioq->buf + ioq->fifo_beg, msg->aux_data,
                              ioq->abytes);
                        msg->aux_size = ioq->abytes;
                        ioq->fifo_beg += ioq->abytes;
+                       if (ioq->fifo_cdx < ioq->fifo_beg)
+                               ioq->fifo_cdx = ioq->fifo_beg;
                        bytes -= ioq->abytes;
                } else if (bytes) {
-                       bcopy(iocom->rxbuf + ioq->fifo_beg, msg->aux_data,
+                       bcopy(ioq->buf + ioq->fifo_beg, msg->aux_data,
                              bytes);
                        msg->aux_size = bytes;
                        ioq->fifo_beg += bytes;
+                       if (ioq->fifo_cdx < ioq->fifo_beg)
+                               ioq->fifo_cdx = ioq->fifo_beg;
                        bytes = 0;
                }
                ioq->state = HAMMER2_MSGQ_STATE_AUXDATA2;
@@ -559,10 +570,7 @@ hammer2_ioq_read(hammer2_iocom_t *iocom)
                        break;
                }
                assert(msg->aux_size == ioq->abytes);
-
-               /*
-                * XXX Decrypt the data
-                */
+               hammer2_crypto_decrypt_aux(iocom, ioq, msg, ioq->already);
 
                /*
                 * Check aux_icrc, then we are done.
@@ -584,6 +592,18 @@ hammer2_ioq_read(hammer2_iocom_t *iocom)
        }
 
        /*
+        * Check the message sequence.  The iv[] should prevent any
+        * possibility of a replay but we add this check anyway.
+        */
+       if (msg && ioq->error == 0) {
+               if ((msg->any.head.salt & 255) != (ioq->seq & 255)) {
+                       ioq->error = HAMMER2_IOQ_ERROR_MSGSEQ;
+               } else {
+                       ++ioq->seq;
+               }
+       }
+
+       /*
         * Handle error, RREQ, or completion
         *
         * NOTE: nmax and bytes are invalid at this point, we don't bother
@@ -626,8 +646,11 @@ hammer2_ioq_read(hammer2_iocom_t *iocom)
                 * Leave the FIFO intact.
                 */
                iocom->flags |= HAMMER2_IOCOMF_RREQ;
+#if 0
+               ioq->fifo_cdx = 0;
                ioq->fifo_beg = 0;
                ioq->fifo_end = 0;
+#endif
        } else {
                /*
                 * Return msg, clear the FIFO if it is now empty.
@@ -639,6 +662,7 @@ hammer2_ioq_read(hammer2_iocom_t *iocom)
                 */
                if (ioq->fifo_beg == ioq->fifo_end) {
                        iocom->flags |= HAMMER2_IOCOMF_RREQ;
+                       ioq->fifo_cdx = 0;
                        ioq->fifo_beg = 0;
                        ioq->fifo_end = 0;
                } else {
@@ -676,11 +700,15 @@ hammer2_ioq_write(hammer2_msg_t *msg)
        }
 
        /*
-        * Finish populating the msg fields
+        * Finish populating the msg fields.  The salt ensures that the iv[]
+        * array is ridiculously randomized and we also re-seed our PRNG
+        * every 32768 messages just to be sure.
         */
        msg->any.head.magic = HAMMER2_MSGHDR_MAGIC;
        msg->any.head.salt = (random() << 8) | (ioq->seq & 255);
        ++ioq->seq;
+       if ((ioq->seq & 32767) == 0)
+               srandomdev();
 
        /*
         * Calculate aux_icrc if 0, calculate icrc2, and finally
@@ -785,6 +813,15 @@ hammer2_iocom_flush(hammer2_iocom_t *iocom)
                return;
 
        /*
+        * Encrypt and write the data.  The crypto code will move the
+        * data into the fifo and adjust the iov as necessary.  If
+        * encryption is disabled the iov is left alone.
+        *
+        * hammer2_crypto_encrypt_wrote()
+        */
+       n = hammer2_crypto_encrypt(iocom, ioq, iov, n);
+
+       /*
         * Execute the writev() then figure out what happened.
         */
        nact = writev(iocom->sock_fd, iov, n);
@@ -799,6 +836,7 @@ hammer2_iocom_flush(hammer2_iocom_t *iocom)
                }
                return;
        }
+       hammer2_crypto_encrypt_wrote(iocom, ioq, nact);
        if (nact == nmax)
                iocom->flags &= ~HAMMER2_IOCOMF_WREQ;
        else
index 6167998..eeaeb20 100644 (file)
  * SUCH DAMAGE.
  */
 
+#include <openssl/rsa.h>       /* public/private key functions */
+#include <openssl/pem.h>       /* public/private key file load */
+#include <openssl/err.h>
+#include <openssl/evp.h>       /* aes_256_cbc functions */
+
 /***************************************************************************
  *                             CRYPTO HANDSHAKE                           *
  ***************************************************************************
  * 512-byte buffer to the other side in a symmetrical fashion.  This
  * buffer contains the following:
  *
- * (1) A random session key.
+ * (1) A random session key.  512 bits is specified.  We use aes_256_cbc()
+ *     and initialize the key with the first 256 bits and the iv[] with
+ *     the second.  Note that the transmitted and received session
+ *     keys are XOR'd together to create the session key used for
+ *     communications (so even if the verifier is compromised the session
+ *     will still be gobbly gook if the public key has not been completely
+ *     broken).
  *
  * (2) A verifier to determine that the decode was successful.  It encodes
  *     an XOR of each group of 4 bytes from the session key.
@@ -73,6 +84,12 @@ struct hammer2_handshake {
 
 typedef struct hammer2_handshake hammer2_handshake_t;
 
+#define HAMMER2_AES_KEY_SIZE   32
+#define HAMMER2_AES_KEY_MASK   (HAMMER2_AES_KEY_SIZE - 1)
+#define HAMMER2_AES_TYPE       aes_256_cbc
+#define HAMMER2_AES_TYPE_EVP   EVP_aes_256_cbc()
+#define HAMMER2_AES_TYPE_STR   #HAMMER2_AES_TYPE
+
 /***************************************************************************
  *                             LOW LEVEL MESSAGING                        *
  ***************************************************************************
@@ -120,14 +137,19 @@ struct hammer2_ioq {
               HAMMER2_MSGQ_STATE_AUXDATA2,
               HAMMER2_MSGQ_STATE_ERROR } state;
        int             fifo_beg;               /* buffered data */
+       int             fifo_cdx;               /* encrypt/decrypt index */
        int             fifo_end;
        int             hbytes;                 /* header size */
        int             abytes;                 /* aux_data size */
+       int             already;                /* aux_data already decrypted */
        int             error;
        int             seq;                    /* salt sequencer */
        int             msgcount;
+       EVP_CIPHER_CTX  ctx;
+       char            iv[HAMMER2_AES_KEY_SIZE]; /* encrypt or decrypt iv[] */
        hammer2_msg_t   *msg;
        hammer2_msg_queue_t msgq;
+       char            buf[HAMMER2_MSGBUF_SIZE]; /* staging buffer */
 };
 
 typedef struct hammer2_ioq hammer2_ioq_t;
@@ -146,6 +168,7 @@ typedef struct hammer2_ioq hammer2_ioq_t;
 #define HAMMER2_IOQ_ERROR_KEYXCHGFAIL  12      /* key exchange failed */
 #define HAMMER2_IOQ_ERROR_KEYFMT       13      /* key file format problem */
 #define HAMMER2_IOQ_ERROR_BADURANDOM   14      /* /dev/urandom is bad */
+#define HAMMER2_IOQ_ERROR_MSGSEQ       15      /* message sequence error */
 
 #define HAMMER2_IOQ_MAXIOVEC    16
 
@@ -165,7 +188,7 @@ struct hammer2_iocom {
        int     flags;
        int     rxmisc;
        int     txmisc;
-       char    rxbuf[HAMMER2_MSGBUF_SIZE];     /* for ioq_rx only */
+       char    sess[HAMMER2_AES_KEY_SIZE];     /* aes_256_cbc key */
 };
 
 typedef struct hammer2_iocom hammer2_iocom_t;
@@ -175,6 +198,7 @@ typedef struct hammer2_iocom hammer2_iocom_t;
 #define HAMMER2_IOCOMF_WREQ    0x00000004      /* request write-avail event */
 #define HAMMER2_IOCOMF_WIDLE   0x00000008      /* request write-avail event */
 #define HAMMER2_IOCOMF_SIGNAL  0x00000010
+#define HAMMER2_IOCOMF_CRYPTED 0x00000020      /* encrypt enabled */
 
 /***************************************************************************
  *                             HIGH LEVEL MESSAGING                       *