[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH 06/20] chacha: packet encryption


From: Aris Adamantiadis <aris@xxxxxxxxxxxx>

---
 include/libssh/crypto.h    |   6 +++
 include/libssh/libcrypto.h |   1 +
 include/libssh/wrapper.h   |   3 +-
 src/CMakeLists.txt         |   1 +
 src/chachapoly.c           | 106 +++++++++++++++++++++++++++++++++++++++++++++
 src/dh.c                   |   3 ++
 src/kex.c                  |   4 +-
 src/libcrypto.c            |  12 ++++-
 src/packet.c               |  36 ++++++++++-----
 src/packet_crypt.c         |  46 ++++++++++++--------
 src/wrapper.c              |  62 +++++++++++++++++---------
 11 files changed, 226 insertions(+), 54 deletions(-)
 create mode 100644 src/chachapoly.c

diff --git a/include/libssh/crypto.h b/include/libssh/crypto.h
index 81915e20..83391b69 100644
--- a/include/libssh/crypto.h
+++ b/include/libssh/crypto.h
@@ -129,10 +129,12 @@ struct ssh_cipher_struct {
     const char *name; /* ssh name of the algorithm */
     unsigned int blocksize; /* blocksize of the algo */
     enum ssh_cipher_e ciphertype;
+    unsigned int lenfield_blocksize; /* blocksize of the packet length field */
 #ifdef HAVE_LIBGCRYPT
     size_t keylen; /* length of the key structure */
     gcry_cipher_hd_t *key;
 #elif defined HAVE_LIBCRYPTO
+    size_t keylen; /* length of the key structure */
     struct ssh_3des_key_schedule *des3_key;
     struct ssh_aes_key_schedule *aes_key;
     const EVP_CIPHER *cipher;
@@ -142,7 +144,9 @@ struct ssh_cipher_struct {
     mbedtls_cipher_context_t decrypt_ctx;
     mbedtls_cipher_type_t type;
 #endif
+    struct chacha20_poly1305_keysched *chacha20_schedule;
     unsigned int keysize; /* bytes of key used. != keylen */
+    size_t tag_size; /* overhead required for tag */
     /* sets the new key for immediate use */
     int (*set_encrypt_key)(struct ssh_cipher_struct *cipher, void *key, void *IV);
     int (*set_decrypt_key)(struct ssh_cipher_struct *cipher, void *key, void *IV);
@@ -150,6 +154,8 @@ struct ssh_cipher_struct {
         unsigned long len);
     void (*decrypt)(struct ssh_cipher_struct *cipher, void *in, void *out,
         unsigned long len);
+    void (*aead_encrypt)(struct ssh_cipher_struct *cipher, void *in, void *out,
+        size_t len, uint8_t *mac, uint64_t seq);
     void (*cleanup)(struct ssh_cipher_struct *cipher);
 };
 
diff --git a/include/libssh/libcrypto.h b/include/libssh/libcrypto.h
index 6a08837a..4b8e5414 100644
--- a/include/libssh/libcrypto.h
+++ b/include/libssh/libcrypto.h
@@ -95,6 +95,7 @@ SHA512CTX sha512_init(void);
 void sha512_update(SHA512CTX c, const void *data, unsigned long len);
 void sha512_final(unsigned char *md, SHA512CTX c);
 
+void libcrypto_init(void);
 struct ssh_cipher_struct *ssh_get_ciphertab(void);
 
 #endif /* HAVE_LIBCRYPTO */
diff --git a/include/libssh/wrapper.h b/include/libssh/wrapper.h
index 6b6cf0b1..c23c9061 100644
--- a/include/libssh/wrapper.h
+++ b/include/libssh/wrapper.h
@@ -39,7 +39,8 @@ enum ssh_hmac_e {
   SSH_HMAC_SHA256,
   SSH_HMAC_SHA384,
   SSH_HMAC_SHA512,
-  SSH_HMAC_MD5
+  SSH_HMAC_MD5,
+  SSH_HMAC_AEAD_POLY1305
 };
 
 enum ssh_des_e {
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index eaa2f067..8f7fdf40 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -122,6 +122,7 @@ set(libssh_SRCS
   bignum.c
   buffer.c
   callbacks.c
+  chachapoly.c
   channels.c
   client.c
   config.c
diff --git a/src/chachapoly.c b/src/chachapoly.c
new file mode 100644
index 00000000..eeb12da8
--- /dev/null
+++ b/src/chachapoly.c
@@ -0,0 +1,106 @@
+/*
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2015 by Aris Adamantiadis
+ *
+ * The SSH Library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or (at your
+ * option) any later version.
+ *
+ * The SSH 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 the SSH Library; see the file COPYING.  If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#include "config.h"
+#include "libssh/libssh.h"
+#include "libssh/crypto.h"
+#include "libssh/chacha.h"
+#include "libssh/poly1305.h"
+#include "libssh/misc.h"
+
+/* size of the keys k1 and k2 as defined in specs */
+#define CHACHA20_KEYLEN 32
+struct chacha20_poly1305_keysched {
+    /* key used for encrypting the length field*/
+    struct chacha_ctx k1;
+    /* key used for encrypting the packets */
+    struct chacha_ctx k2;
+};
+
+struct ssh_packet_header {
+    uint32_t length;
+    uint8_t payload[];
+} __attribute__((packed));
+
+const uint8_t zero_block_counter[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+const uint8_t payload_block_counter[8] = {1, 0, 0, 0, 0, 0, 0, 0};
+
+static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher,
+        void *key, void *IV){
+    struct chacha20_poly1305_keysched *sched;
+    uint8_t *key2 = key;
+    (void)IV;
+
+    if(cipher->chacha20_schedule == NULL){
+        sched = malloc(sizeof *sched);
+        if (sched == NULL){
+            return -1;
+        }
+    } else {
+        sched = cipher->chacha20_schedule;
+    }
+    chacha_keysetup(&sched->k2, key2, CHACHA20_KEYLEN * 8);
+    chacha_keysetup(&sched->k1, key2 + CHACHA20_KEYLEN, CHACHA20_KEYLEN * 8);
+    cipher->chacha20_schedule = sched;
+    return 0;
+}
+
+/** @internal
+ * @brief encrypts an outgoing packet with chacha20 and authenticate it
+ * with poly1305.
+ */
+static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher,
+        void *in, void *out, size_t len, uint8_t *tag, uint64_t seq){
+    struct ssh_packet_header *in_packet = in, *out_packet = out;
+    uint8_t poly1305_ctx[POLY1305_KEYLEN];
+    struct chacha20_poly1305_keysched *keys = cipher->chacha20_schedule;
+
+    seq = htonll(seq);
+    /* step 1, prepare the poly1305 key */
+    ZERO_STRUCT(poly1305_ctx);
+    chacha_ivsetup(&keys->k2, (uint8_t *)&seq, zero_block_counter);
+    chacha_encrypt_bytes(&keys->k2, poly1305_ctx, poly1305_ctx, POLY1305_KEYLEN);
+
+    /* step 2, encrypt length field */
+    chacha_ivsetup(&keys->k1, (uint8_t *)&seq, zero_block_counter);
+    chacha_encrypt_bytes(&keys->k1,(uint8_t *)&in_packet->length,
+            (uint8_t *)&out_packet->length, sizeof(uint32_t));
+
+    /* step 3, encrypt packet payload */
+    chacha_ivsetup(&keys->k2, (uint8_t *)&seq, payload_block_counter);
+    chacha_encrypt_bytes(&keys->k2, in_packet->payload, out_packet->payload,
+                len - sizeof(uint32_t));
+
+    /* step 4, compute the MAC */
+    poly1305_auth(tag, (uint8_t *)out_packet, len, poly1305_ctx);
+}
+
+const struct ssh_cipher_struct chacha20poly1305_cipher = {
+    .name = "chacha20-poly1305@xxxxxxxxxxx",
+    .blocksize = 8,
+    .lenfield_blocksize = 4,
+    .keylen = sizeof(struct chacha20_poly1305_keysched),
+    .keysize = 512,
+    .tag_size = POLY1305_TAGLEN,
+    .set_encrypt_key = chacha20_set_encrypt_key,
+    .set_decrypt_key = chacha20_set_encrypt_key,
+    .aead_encrypt = chacha20_poly1305_aead_encrypt,
+};
diff --git a/src/dh.c b/src/dh.c
index 26e47994..f29627c5 100644
--- a/src/dh.c
+++ b/src/dh.c
@@ -68,6 +68,7 @@
 #include <openssl/rand.h>
 #include <openssl/evp.h>
 #include <openssl/err.h>
+#include "libssh/libcrypto.h"
 #endif
 
 static unsigned char p_group1_value[] = {
@@ -210,6 +211,8 @@ int ssh_crypto_init(void) {
     bignum_bin2bn(p_group14_value, P_GROUP14_LEN, p_group14);
 
     OpenSSL_add_all_algorithms();
+
+    libcrypto_init();
 #elif defined HAVE_LIBMBEDCRYPTO
     p_group1 = bignum_new();
     bignum_bin2bn(p_group1_value, P_GROUP1_LEN, p_group1);
diff --git a/src/kex.c b/src/kex.c
index b658ed44..20044c32 100644
--- a/src/kex.c
+++ b/src/kex.c
@@ -95,6 +95,8 @@
 #define ECDH ""
 #endif
 
+#define CHACHA20 "chacha20-poly1305@xxxxxxxxxxx,"
+
 #define KEY_EXCHANGE CURVE25519 ECDH "diffie-hellman-group14-sha1,diffie-hellman-group1-sha1"
 #define KEX_METHODS_SIZE 10
 
@@ -117,7 +119,7 @@ static const char *default_methods[] = {
 static const char *supported_methods[] = {
   KEY_EXCHANGE,
   HOSTKEYS,
-  AES BLOWFISH DES_SUPPORTED,
+  CHACHA20 AES BLOWFISH DES_SUPPORTED,
   AES BLOWFISH DES_SUPPORTED,
   "hmac-sha2-256,hmac-sha2-512,hmac-sha1",
   "hmac-sha2-256,hmac-sha2-512,hmac-sha1",
diff --git a/src/libcrypto.c b/src/libcrypto.c
index 66453666..0ed7b7bb 100644
--- a/src/libcrypto.c
+++ b/src/libcrypto.c
@@ -60,6 +60,7 @@
 
 #include "libssh/crypto.h"
 
+extern const struct ssh_cipher_struct chacha20poly1305_cipher;
 struct ssh_mac_ctx_struct {
   enum ssh_mac_e mac_type;
   union {
@@ -861,10 +862,19 @@ static struct ssh_cipher_struct ssh_ciphertab[] = {
   },
 #endif /* HAS_DES */
   {
-    .name = NULL
+    .name = "chacha20-poly1305@xxxxxxxxxxx"
   }
 };
 
+void libcrypto_init(void){
+    int i;
+    for (i=0; ssh_ciphertab[i].name != NULL; ++i){
+        if(strcmp(ssh_ciphertab[i].name, "chacha20-poly1305@xxxxxxxxxxx") == 0){
+            memcpy(&ssh_ciphertab[i], &chacha20poly1305_cipher, sizeof(struct ssh_cipher_struct));
+            break;
+        }
+    }
+}
 
 struct ssh_cipher_struct *ssh_get_ciphertab(void)
 {
diff --git a/src/packet.c b/src/packet.c
index 57eacc4d..e989fa28 100644
--- a/src/packet.c
+++ b/src/packet.c
@@ -547,6 +547,8 @@ static int ssh_packet_write(ssh_session session) {
 static int packet_send2(ssh_session session) {
   unsigned int blocksize = (session->current_crypto ?
       session->current_crypto->out_cipher->blocksize : 8);
+  unsigned int lenfield_blocksize = (session->current_crypto ?
+      session->current_crypto->out_cipher->lenfield_blocksize : 0);
   enum ssh_hmac_e hmac_type = (session->current_crypto ?
       session->current_crypto->out_hmac : session->next_crypto->out_hmac);
   uint32_t currentlen = ssh_buffer_get_len(session->out_buffer);
@@ -555,8 +557,7 @@ static int packet_send2(ssh_session session) {
   int rc = SSH_ERROR;
   uint32_t finallen,payloadsize,compsize;
   uint8_t padding;
-
-  uint8_t header[sizeof(padding) + sizeof(finallen)] = { 0 };
+  ssh_buffer header_buffer = ssh_buffer_new();
 
   payloadsize = currentlen;
 #ifdef WITH_ZLIB
@@ -570,20 +571,29 @@ static int packet_send2(ssh_session session) {
   }
 #endif /* WITH_ZLIB */
   compsize = currentlen;
-  padding = (blocksize - ((currentlen +5) % blocksize));
+  /* compressed payload + packet len (4) + padding len (1) */
+  /* totallen - lenfield_blocksize must be equal to 0 (mod blocksize) */
+  padding = (blocksize - ((blocksize - lenfield_blocksize + currentlen + 5) % blocksize));
   if(padding < 4) {
     padding += blocksize;
   }
 
-  if (session->current_crypto) {
+  if (session->current_crypto != NULL) {
     ssh_get_random(padstring, padding, 0);
   }
 
-  finallen = htonl(currentlen + padding + 1);
+  if (header_buffer == NULL){
+    ssh_set_error_oom(session);
+    goto error;
+  }
+  finallen = currentlen + padding + 1;
+  rc = ssh_buffer_pack(header_buffer, "db", finallen, padding);
+  if (rc == SSH_ERROR){
+    goto error;
+  }
 
-  memcpy(&header[0], &finallen, sizeof(finallen));
-  header[sizeof(finallen)] = padding;
-  rc = ssh_buffer_prepend_data(session->out_buffer, &header, sizeof(header));
+  rc = ssh_buffer_prepend_data(session->out_buffer, ssh_buffer_get(header_buffer),
+          ssh_buffer_get_len(header_buffer));
   if (rc < 0) {
     goto error;
   }
@@ -594,8 +604,8 @@ static int packet_send2(ssh_session session) {
 #ifdef WITH_PCAP
   if(session->pcap_ctx){
   	ssh_pcap_context_write(session->pcap_ctx,SSH_PCAP_DIR_OUT,
-  			ssh_buffer_get(session->out_buffer),ssh_buffer_get_len(session->out_buffer)
-  			,ssh_buffer_get_len(session->out_buffer));
+  			ssh_buffer_get(session->out_buffer),ssh_buffer_get_len(session->out_buffer),
+  			ssh_buffer_get_len(session->out_buffer));
   }
 #endif
   hmac = ssh_packet_encrypt(session, ssh_buffer_get(session->out_buffer),
@@ -616,12 +626,14 @@ static int packet_send2(ssh_session session) {
 
   SSH_LOG(SSH_LOG_PACKET,
           "packet: wrote [len=%d,padding=%hhd,comp=%d,payload=%d]",
-          ntohl(finallen), padding, compsize, payloadsize);
+          finallen, padding, compsize, payloadsize);
   if (ssh_buffer_reinit(session->out_buffer) < 0) {
     rc = SSH_ERROR;
   }
 error:
-
+  if(header_buffer != NULL){
+    ssh_buffer_free(header_buffer);
+  }
   return rc; /* SSH_OK, AGAIN or ERROR */
 }
 
diff --git a/src/packet_crypt.c b/src/packet_crypt.c
index 7a30e661..5bcb6d65 100644
--- a/src/packet_crypt.c
+++ b/src/packet_crypt.c
@@ -93,7 +93,7 @@ unsigned char *ssh_packet_encrypt(ssh_session session, void *data, uint32_t len)
   if (!session->current_crypto) {
     return NULL; /* nothing to do here */
   }
-  if(len % session->current_crypto->in_cipher->blocksize != 0){
+  if((len - session->current_crypto->out_cipher->lenfield_blocksize) % session->current_crypto->out_cipher->blocksize != 0){
       ssh_set_error(session, SSH_FATAL, "Cryptographic functions must be set on at least one blocksize (received %d)",len);
       return NULL;
   }
@@ -106,26 +106,36 @@ unsigned char *ssh_packet_encrypt(ssh_session session, void *data, uint32_t len)
   seq = ntohl(session->send_seq);
   crypto = session->current_crypto->out_cipher;
 
-  if (session->version == 2) {
-    ctx = hmac_init(session->current_crypto->encryptMAC, hmac_digest_len(type), type);
-    if (ctx == NULL) {
-      SAFE_FREE(out);
-      return NULL;
-    }
-    hmac_update(ctx,(unsigned char *)&seq,sizeof(uint32_t));
-    hmac_update(ctx,data,len);
-    hmac_final(ctx,session->current_crypto->hmacbuf,&finallen);
+  if (crypto->aead_encrypt != NULL) {
+    crypto->aead_encrypt(crypto, data, out, len,
+            session->current_crypto->hmacbuf, session->send_seq);
+  } else {
+    if (session->version == 2) {
+      ctx = hmac_init(session->current_crypto->encryptMAC, hmac_digest_len(type), type);
+      if (ctx == NULL) {
+        SAFE_FREE(out);
+        return NULL;
+      }
+      hmac_update(ctx,(unsigned char *)&seq,sizeof(uint32_t));
+      hmac_update(ctx,data,len);
+      hmac_final(ctx,session->current_crypto->hmacbuf,&finallen);
+
+      if (crypto->set_encrypt_key(crypto, session->current_crypto->encryptkey,
+          session->current_crypto->encryptIV) < 0) {
+        SAFE_FREE(out);
+        return NULL;
+      }
+
 #ifdef DEBUG_CRYPTO
-    ssh_print_hexa("mac: ",data,hmac_digest_len(type));
-    if (finallen != hmac_digest_len(type)) {
-      printf("Final len is %d\n",finallen);
-    }
-    ssh_print_hexa("Packet hmac", session->current_crypto->hmacbuf, hmac_digest_len(type));
+      ssh_print_hexa("mac: ",data,hmac_digest_len(type));
+      if (finallen != hmac_digest_len(type)) {
+        printf("Final len is %d\n",finallen);
+      }
+      ssh_print_hexa("Packet hmac", session->current_crypto->hmacbuf, hmac_digest_len(type));
 #endif
-  }
-
+    }
   crypto->encrypt(crypto, data, out, len);
-
+  }
   memcpy(data, out, len);
   explicit_bzero(out, len);
   SAFE_FREE(out);
diff --git a/src/wrapper.c b/src/wrapper.c
index 7ecb3101..71d64bf3 100644
--- a/src/wrapper.c
+++ b/src/wrapper.c
@@ -47,6 +47,7 @@
 #include "libssh/crypto.h"
 #include "libssh/wrapper.h"
 #include "libssh/pki.h"
+#include "libssh/poly1305.h"
 
 static struct ssh_hmac_struct ssh_hmac_tab[] = {
   { "hmac-sha1",     SSH_HMAC_SHA1 },
@@ -54,6 +55,7 @@ static struct ssh_hmac_struct ssh_hmac_tab[] = {
   { "hmac-sha2-384", SSH_HMAC_SHA384 },
   { "hmac-sha2-512", SSH_HMAC_SHA512 },
   { "hmac-md5",      SSH_HMAC_MD5 },
+  { "aead-poly1305", SSH_HMAC_AEAD_POLY1305 },
   { NULL,            0}
 };
 
@@ -73,6 +75,8 @@ size_t hmac_digest_len(enum ssh_hmac_e type) {
       return SHA512_DIGEST_LEN;
     case SSH_HMAC_MD5:
       return MD5_DIGEST_LEN;
+    case SSH_HMAC_AEAD_POLY1305:
+      return POLY1305_TAGLEN;
     default:
       return 0;
   }
@@ -124,6 +128,9 @@ void ssh_cipher_clear(struct ssh_cipher_struct *cipher){
     if (cipher->cleanup != NULL) {
         cipher->cleanup(cipher);
     }
+    if (cipher->chacha20_schedule != NULL){
+        SAFE_FREE(cipher->chacha20_schedule);
+    }
 }
 
 static void cipher_free(struct ssh_cipher_struct *cipher) {
@@ -247,9 +254,14 @@ static int crypt_set_algorithms2(ssh_session session){
   }
   i = 0;
 
-  /* we must scan the kex entries to find hmac algorithms and set their appropriate structure */
-  /* out */
-  wanted = session->next_crypto->kex_methods[SSH_MAC_C_S];
+  if (session->next_crypto->out_cipher->aead_encrypt != NULL){
+      /* this cipher has integrated MAC */
+      wanted = "aead-poly1305";
+  } else {
+      /* we must scan the kex entries to find hmac algorithms and set their appropriate structure */
+      /* out */
+      wanted = session->next_crypto->kex_methods[SSH_MAC_C_S];
+  }
   while (ssh_hmactab[i].name && strcmp(wanted, ssh_hmactab[i].name)) {
     i++;
   }
@@ -357,7 +369,7 @@ int crypt_set_algorithms(ssh_session session, enum ssh_des_e des_type) {
 
 #ifdef WITH_SERVER
 int crypt_set_algorithms_server(ssh_session session){
-    char *method = NULL;
+    const char *method = NULL;
     int i = 0;
     struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab();
     struct ssh_hmac_struct   *ssh_hmactab=ssh_get_hmactab();
@@ -387,7 +399,32 @@ int crypt_set_algorithms_server(ssh_session session){
         return SSH_ERROR;
     }
     i=0;
+    if (session->next_crypto->out_cipher->aead_encrypt != NULL){
+        /* this cipher has integrated MAC */
+        method = "aead-poly1305";
+    } else {
+        /* we must scan the kex entries to find hmac algorithms and set their appropriate structure */
+        /* out */
+        method = session->next_crypto->kex_methods[SSH_MAC_S_C];
+    }
+    /* HMAC algorithm selection */
+
+    while (ssh_hmactab[i].name && strcmp(method, ssh_hmactab[i].name)) {
+      i++;
+    }
+
+    if (ssh_hmactab[i].name == NULL) {
+      ssh_set_error(session, SSH_FATAL,
+          "crypt_set_algorithms_server: no hmac algorithm function found for %s",
+          method);
+        return SSH_ERROR;
+    }
+    SSH_LOG(SSH_LOG_PACKET, "Set HMAC output algorithm to %s", method);
+
+    session->next_crypto->out_hmac = ssh_hmactab[i].hmac_type;
+
     /* in */
+    i=0;
     method = session->next_crypto->kex_methods[SSH_CRYPT_C_S];
     while(ssh_ciphertab[i].name && strcmp(method,ssh_ciphertab[i].name))
         i++;
@@ -405,23 +442,6 @@ int crypt_set_algorithms_server(ssh_session session){
     }
     i=0;
 
-    /* HMAC algorithm selection */
-    method = session->next_crypto->kex_methods[SSH_MAC_S_C];
-    while (ssh_hmactab[i].name && strcmp(method, ssh_hmactab[i].name)) {
-      i++;
-    }
-
-    if (ssh_hmactab[i].name == NULL) {
-      ssh_set_error(session, SSH_FATAL,
-          "crypt_set_algorithms_server: no hmac algorithm function found for %s",
-          method);
-        return SSH_ERROR;
-    }
-    SSH_LOG(SSH_LOG_PACKET, "Set HMAC output algorithm to %s", method);
-
-    session->next_crypto->out_hmac = ssh_hmactab[i].hmac_type;
-    i=0;
-
     method = session->next_crypto->kex_methods[SSH_MAC_C_S];
     while (ssh_hmactab[i].name && strcmp(method, ssh_hmactab[i].name)) {
       i++;
-- 
2.14.1


References:
[PATCH 00/20] Add chacha20-poly1305 supportAlberto Aguirre <albaguirre@xxxxxxxxx>
Archive administrator: postmaster@lists.cynapses.org