hammer2 - further crypto cleanup
[dragonfly.git] / sbin / hammer2 / crypto.c
index f945fd1..fd5ac6f 100644 (file)
@@ -4,6 +4,7 @@
  * This code is derived from software contributed to The DragonFly Project
  * by Matthew Dillon <dillon@dragonflybsd.org>
  * by Venkatesh Srinivas <vsrinivas@dragonflybsd.org>
+ * by Alex Hornung <alexh@dragonflybsd.org>
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -34,6 +35,7 @@
  */
 
 #include "hammer2.h"
+#include <sys/endian.h>
 
 /*
  * Setup crypto for pthreads
 static pthread_mutex_t *crypto_locks;
 int crypto_count;
 
+static int hammer2_crypto_gcm_init(hammer2_ioq_t *, char *, int, char *, int, int);
+static int hammer2_crypto_gcm_encrypt_chunk(hammer2_ioq_t *, char *, char *, int, int *);
+static int hammer2_crypto_gcm_decrypt_chunk(hammer2_ioq_t *, char *, char *, int, int *);
+
+/*
+ * NOTE: the order of this table needs to match the HAMMER2_CRYPTO_ALGO_*_IDX
+ *       defines in network.h.
+ */
+static struct crypto_algo crypto_algos[] = {
+       {
+               .name      = "aes-256-gcm",
+               .keylen    = HAMMER2_CRYPTO_GCM_KEY_SIZE,
+               .taglen    = HAMMER2_CRYPTO_GCM_TAG_SIZE,
+               .init      = hammer2_crypto_gcm_init,
+               .enc_chunk = hammer2_crypto_gcm_encrypt_chunk,
+               .dec_chunk = hammer2_crypto_gcm_decrypt_chunk
+       },
+       { NULL, 0, 0, NULL, NULL, NULL }
+};
+
 static
 unsigned long
 hammer2_crypto_id_callback(void)
@@ -70,6 +92,208 @@ hammer2_crypto_setup(void)
        CRYPTO_set_locking_callback(hammer2_crypto_locking_callback);
 }
 
+static
+int
+hammer2_crypto_gcm_init(hammer2_ioq_t *ioq, char *key, int klen,
+                       char *iv_fixed, int ivlen, int enc)
+{
+       int i, ok;
+
+       if (klen < HAMMER2_CRYPTO_GCM_KEY_SIZE ||
+           ivlen < HAMMER2_CRYPTO_GCM_IV_FIXED_SIZE) {
+               if (DebugOpt)
+                       fprintf(stderr, "Not enough key or iv material\n");
+               return -1;
+       }
+
+       printf("%s key: ", enc ? "Encryption" : "Decryption");
+       for (i = 0; i < HAMMER2_CRYPTO_GCM_KEY_SIZE; ++i)
+               printf("%02x", (unsigned char)key[i]);
+       printf("\n");
+
+       printf("%s iv:  ", enc ? "Encryption" : "Decryption");
+       for (i = 0; i < HAMMER2_CRYPTO_GCM_IV_FIXED_SIZE; ++i)
+               printf("%02x", (unsigned char)iv_fixed[i]);
+       printf(" (fixed part only)\n");
+
+       EVP_CIPHER_CTX_init(&ioq->ctx);
+
+       if (enc)
+               ok = EVP_EncryptInit_ex(&ioq->ctx, EVP_aes_256_gcm(), NULL,
+                                       key, NULL);
+       else
+               ok = EVP_DecryptInit_ex(&ioq->ctx, EVP_aes_256_gcm(), NULL,
+                                       key, NULL);
+       if (!ok)
+               goto fail;
+
+       /*
+        * According to the original Galois/Counter Mode of Operation (GCM)
+        * proposal, only IVs that are exactly 96 bits get used without any
+        * further processing. Other IV sizes cause the GHASH() operation
+        * to be applied to the IV, which is more costly.
+        *
+        * The NIST SP 800-38D also recommends using a 96 bit IV for the same
+        * reasons. We actually follow the deterministic construction
+        * recommended in NIST SP 800-38D with a 64 bit invocation field as an
+        * integer counter and a random, session-specific fixed field.
+        *
+        * This means that we can essentially use the same session key and
+        * IV fixed field for up to 2^64 invocations of the authenticated
+        * encryption or decryption.
+        *
+        * With a chunk size of 64 bytes, this adds up to 1 zettabyte of
+        * traffic.
+        */
+       ok = EVP_CIPHER_CTX_ctrl(&ioq->ctx, EVP_CTRL_GCM_SET_IVLEN,
+                                HAMMER2_CRYPTO_GCM_IV_SIZE, NULL);
+       if (!ok)
+               goto fail;
+
+       memset(ioq->iv, 0, HAMMER2_CRYPTO_GCM_IV_SIZE);
+       memcpy(ioq->iv, iv_fixed, HAMMER2_CRYPTO_GCM_IV_FIXED_SIZE);
+
+       /*
+        * Strictly speaking, padding is irrelevant with a counter mode
+        * encryption.
+        *
+        * However, setting padding to 0, even if using a counter mode such
+        * as GCM, will cause an error in _finish if the pt/ct size is not
+        * a multiple of the cipher block size.
+        */
+       EVP_CIPHER_CTX_set_padding(&ioq->ctx, 0);
+
+       return 0;
+
+fail:
+       if (DebugOpt)
+               fprintf(stderr, "Error during _gcm_init\n");
+       return -1;
+}
+
+static
+int
+_gcm_iv_increment(char *iv)
+{
+       /*
+        * Deterministic construction according to NIST SP 800-38D, with
+        * 64 bit invocation field as integer counter.
+        *
+        * In other words, our 96 bit IV consists of a 32 bit fixed field
+        * unique to the session and a 64 bit integer counter.
+        */
+
+       uint64_t *c = (uint64_t *)(&iv[HAMMER2_CRYPTO_GCM_IV_FIXED_SIZE]);
+
+       /* Increment invocation field integer counter */
+       *c = htobe64(be64toh(*c)+1);
+
+       /*
+        * Detect wrap-around, which means it is time to renegotiate
+        * the session to get a new key and/or fixed field.
+        */
+       return (c == 0) ? 0 : 1;
+}
+
+static
+int
+hammer2_crypto_gcm_encrypt_chunk(hammer2_ioq_t *ioq, char *ct, char *pt,
+                                int in_size, int *out_size)
+{
+       int ok;
+       int u_len, f_len;
+
+       *out_size = 0;
+
+       /* Re-initialize with new IV (but without redoing the key schedule) */
+       ok = EVP_EncryptInit_ex(&ioq->ctx, NULL, NULL, NULL, ioq->iv);
+       if (!ok)
+               goto fail;
+
+       ok = EVP_EncryptUpdate(&ioq->ctx, ct, &u_len, pt, in_size);
+       if (!ok)
+               goto fail;
+
+       ok = EVP_EncryptFinal(&ioq->ctx, ct + u_len, &f_len);
+       if (!ok)
+               goto fail;
+
+       /* Retrieve auth tag */
+       ok = EVP_CIPHER_CTX_ctrl(&ioq->ctx, EVP_CTRL_GCM_GET_TAG,
+                                HAMMER2_CRYPTO_GCM_TAG_SIZE,
+                                ct + u_len + f_len);
+       if (!ok)
+               goto fail;
+
+       ok = _gcm_iv_increment(ioq->iv);
+       if (!ok) {
+               ioq->error = HAMMER2_IOQ_ERROR_IVWRAP;
+               goto fail_out;
+       }
+
+       *out_size = u_len + f_len + HAMMER2_CRYPTO_GCM_TAG_SIZE;
+
+       return 0;
+
+fail:
+       ioq->error = HAMMER2_IOQ_ERROR_ALGO;
+fail_out:
+       if (DebugOpt)
+               fprintf(stderr, "error during encrypt_chunk\n");
+       return -1;
+}
+
+static
+int
+hammer2_crypto_gcm_decrypt_chunk(hammer2_ioq_t *ioq, char *ct, char *pt,
+                                int out_size, int *consume_size)
+{
+       int ok;
+       int u_len, f_len;
+
+       *consume_size = 0;
+
+       /* Re-initialize with new IV (but without redoing the key schedule) */
+       ok = EVP_DecryptInit_ex(&ioq->ctx, NULL, NULL, NULL, ioq->iv);
+       if (!ok) {
+               ioq->error = HAMMER2_IOQ_ERROR_ALGO;
+               goto fail_out;
+       }
+
+       ok = EVP_CIPHER_CTX_ctrl(&ioq->ctx, EVP_CTRL_GCM_SET_TAG,
+                                HAMMER2_CRYPTO_GCM_TAG_SIZE,
+                                ct + out_size);
+       if (!ok) {
+               ioq->error = HAMMER2_IOQ_ERROR_ALGO;
+               goto fail_out;
+       }
+
+       ok = EVP_DecryptUpdate(&ioq->ctx, pt, &u_len, ct, out_size);
+       if (!ok)
+               goto fail;
+
+       ok = EVP_DecryptFinal(&ioq->ctx, pt + u_len, &f_len);
+       if (!ok)
+               goto fail;
+
+       ok = _gcm_iv_increment(ioq->iv);
+       if (!ok) {
+               ioq->error = HAMMER2_IOQ_ERROR_IVWRAP;
+               goto fail_out;
+       }
+
+       *consume_size = u_len + f_len + HAMMER2_CRYPTO_GCM_TAG_SIZE;
+
+       return 0;
+
+fail:
+       ioq->error = HAMMER2_IOQ_ERROR_MACFAIL;
+fail_out:
+       if (DebugOpt)
+               fprintf(stderr, "error during decrypt_chunk (likely authentication error)\n");
+       return -1;
+}
+
 /*
  * Synchronously negotiate crypto for a new session.  This must occur
  * within 10 seconds or the connection is error'd out.
@@ -135,6 +359,7 @@ hammer2_crypto_negotiate(hammer2_iocom_t *iocom)
        size_t blkmask;
        ssize_t n;
        int fd;
+       int error;
 
        /*
         * Get the peer IP address for the connection as a string.
@@ -409,34 +634,24 @@ keyxchgfail:
                goto keyxchgfail;
 
        /*
-        * Calculate the session key and initialize the iv[].
+        * Use separate session keys and session fixed IVs for receive and
+        * transmit.
         */
-       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);
+       error = crypto_algos[HAMMER2_CRYPTO_ALGO].init(&iocom->ioq_rx, handrx.sess,
+           crypto_algos[HAMMER2_CRYPTO_ALGO].keylen,
+           handrx.sess + crypto_algos[HAMMER2_CRYPTO_ALGO].keylen,
+           sizeof(handrx.sess) - crypto_algos[HAMMER2_CRYPTO_ALGO].keylen,
+           0 /* decryption */);
+       if (error)
+               goto keyxchgfail;
 
-       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);
+       error = crypto_algos[HAMMER2_CRYPTO_ALGO].init(&iocom->ioq_tx, handtx.sess,
+           crypto_algos[HAMMER2_CRYPTO_ALGO].keylen,
+           handtx.sess + crypto_algos[HAMMER2_CRYPTO_ALGO].keylen,
+           sizeof(handtx.sess) - crypto_algos[HAMMER2_CRYPTO_ALGO].keylen,
+           1 /* encryption */);
+       if (error)
+               goto keyxchgfail;
 
        iocom->flags |= HAMMER2_IOCOMF_CRYPTED;
 
@@ -457,119 +672,96 @@ done:
  * 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)
+hammer2_crypto_decrypt(hammer2_iocom_t *iocom __unused, hammer2_ioq_t *ioq)
 {
        int p_len;
-       int n;
-       int i;
+       int used;
+       int error;
        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;
+       /*
+        * fifo_beg to fifo_cdx is data already decrypted.
+        * fifo_cdn to fifo_end is data not yet decrypted.
+        */
+       p_len = ioq->fifo_end - ioq->fifo_cdn; /* data not yet decrypted */
+
        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);
+
+       while (p_len >= crypto_algos[HAMMER2_CRYPTO_ALGO].taglen +
+           HAMMER2_CRYPTO_CHUNK_SIZE) {
+               bcopy(ioq->buf + ioq->fifo_cdn, buf,
+                     crypto_algos[HAMMER2_CRYPTO_ALGO].taglen +
+                     HAMMER2_CRYPTO_CHUNK_SIZE);
+               error = crypto_algos[HAMMER2_CRYPTO_ALGO].dec_chunk(
+                   ioq, buf,
+                   ioq->buf + ioq->fifo_cdx,
+                   HAMMER2_CRYPTO_CHUNK_SIZE,
+                   &used);
+#ifdef CRYPTO_DEBUG
+               printf("dec: p_len: %d, used: %d, fifo_cdn: %ju, fifo_cdx: %ju\n",
+                      p_len, used, ioq->fifo_cdn, ioq->fifo_cdx);
+#endif
+               p_len -= used;
+               ioq->fifo_cdn += used;
+               ioq->fifo_cdx += HAMMER2_CRYPTO_CHUNK_SIZE;
+#ifdef CRYPTO_DEBUG
+               printf("dec: p_len: %d, used: %d, fifo_cdn: %ju, fifo_cdx: %ju\n",
+                      p_len, used, ioq->fifo_cdn, ioq->fifo_cdx);
+#endif
        }
-       ioq->fifo_cdx += p_len;
 }
 
 /*
- * Decrypt data in the message's auxilary buffer.  The data is decrypted
- * in-place.
+ * *nactp is set to the number of ORIGINAL bytes consumed by the encrypter.
+ * The FIFO may contain more data.
  */
-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, size_t *nmaxp)
+hammer2_crypto_encrypt(hammer2_iocom_t *iocom __unused, hammer2_ioq_t *ioq,
+                      struct iovec *iov, int n, size_t *nactp)
 {
-       int p_len;
+       int p_len, used, ct_used;
        int i;
-       int already;
-       int nmax;
+       int error;
+       size_t 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 */
+       nmax = sizeof(ioq->buf) - ioq->fifo_end;        /* max new bytes */
 
-       for (i = 0; i < n; ++i) {
+       *nactp = 0;
+       for (i = 0; i < n && nmax; ++i) {
+               used = 0;
                p_len = iov[i].iov_len;
-               if (p_len <= already) {
-                       already -= p_len;
-                       continue;
+               assert((p_len & HAMMER2_MSG_ALIGNMASK) == 0);
+
+               while (p_len >= HAMMER2_CRYPTO_CHUNK_SIZE &&
+                   nmax >= HAMMER2_CRYPTO_CHUNK_SIZE +
+                   (size_t)crypto_algos[HAMMER2_CRYPTO_ALGO].taglen) {
+                       error = crypto_algos[HAMMER2_CRYPTO_ALGO].enc_chunk(
+                           ioq,
+                           ioq->buf + ioq->fifo_cdx,
+                           (char *)iov[i].iov_base + used,
+                           HAMMER2_CRYPTO_CHUNK_SIZE, &ct_used);
+#ifdef CRYPTO_DEBUG
+                       printf("nactp: %ju, p_len: %d, ct_used: %d, used: %d, nmax: %ju\n",
+                              *nactp, p_len, ct_used, used, nmax);
+#endif
+
+                       *nactp += (size_t)HAMMER2_CRYPTO_CHUNK_SIZE;    /* plaintext count */
+                       used += HAMMER2_CRYPTO_CHUNK_SIZE;
+                       p_len -= HAMMER2_CRYPTO_CHUNK_SIZE;
+
+                       ioq->fifo_cdx += (size_t)ct_used;       /* crypted count */
+                       ioq->fifo_cdn += (size_t)ct_used;       /* crypted count */
+                       ioq->fifo_end += (size_t)ct_used;
+                       nmax -= (size_t)ct_used;
+#ifdef CRYPTO_DEBUG
+                       printf("nactp: %ju, p_len: %d, ct_used: %d, used: %d, nmax: %ju\n",
+                              *nactp, p_len, ct_used, used, nmax);
+#endif
                }
-               p_len -= already;
-               p_len &= ~HAMMER2_AES_KEY_MASK;
-               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;
-       *nmaxp = (size_t)(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;
-       }
-}