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

[PATCH 2/2] libgcrypt: Implement chacha20-poly1305@xxxxxxxxxxx cipher using libgcrypt


Libgcrypt has supported ChaCha20 and Poly1305 since 1.7.0 version and
provides fast assembler implementations.

Signed-off-by: Jussi Kivilinna <jussi.kivilinna@xxxxxx>
---
 ConfigureChecks.cmake |   3 +
 config.h.cmake        |   3 +
 src/libgcrypt.c       | 252 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 258 insertions(+)

diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake
index c8bb2aa0..a99278f7 100644
--- a/ConfigureChecks.cmake
+++ b/ConfigureChecks.cmake
@@ -278,6 +278,9 @@ if (GCRYPT_FOUND)
         set(HAVE_GCRYPT_ECC 1)
         set(HAVE_ECC 1)
     endif (GCRYPT_VERSION VERSION_GREATER "1.4.6")
+    if (NOT GCRYPT_VERSION VERSION_LESS "1.7.0")
+        set(HAVE_GCRYPT_CHACHA_POLY 1)
+    endif (NOT GCRYPT_VERSION VERSION_LESS "1.7.0")
 endif (GCRYPT_FOUND)
 
 if (MBEDTLS_FOUND)
diff --git a/config.h.cmake b/config.h.cmake
index 98a72f65..847fc579 100644
--- a/config.h.cmake
+++ b/config.h.cmake
@@ -103,6 +103,9 @@
 /* Define to 1 if you have OpenSSL with X25519 support */
 #cmakedefine HAVE_OPENSSL_X25519 1
 
+/* Define to 1 if you have gcrypt with ChaCha20/Poly1305 support */
+#cmakedefine HAVE_GCRYPT_CHACHA_POLY 1
+
 /*************************** FUNCTIONS ***************************/
 
 /* Define to 1 if you have the `EVP_aes128_ctr' function. */
diff --git a/src/libgcrypt.c b/src/libgcrypt.c
index df192392..72f6661c 100644
--- a/src/libgcrypt.c
+++ b/src/libgcrypt.c
@@ -36,6 +36,34 @@
 #ifdef HAVE_LIBGCRYPT
 #include <gcrypt.h>
 
+#ifdef HAVE_GCRYPT_CHACHA_POLY
+
+#define CHACHA20_BLOCKSIZE 64
+#define CHACHA20_KEYLEN 32
+
+#define POLY1305_TAGLEN 16
+#define POLY1305_KEYLEN 32
+
+struct chacha20_poly1305_keysched {
+    int initialized;
+    /* cipher handle used for encrypting the length field */
+    gcry_cipher_hd_t main_hd;
+    /* cipher handle used for encrypting the packets */
+    gcry_cipher_hd_t header_hd;
+    /* mac handle used for authenticating the packets */
+    gcry_mac_hd_t mac_hd;
+};
+
+#pragma pack(push, 1)
+struct ssh_packet_header {
+    uint32_t length;
+    uint8_t payload[];
+};
+#pragma pack(pop)
+
+static const uint8_t zero_block[CHACHA20_BLOCKSIZE] = {0};
+#endif /* HAVE_GCRYPT_CHACHA_POLY */
+
 static int libgcrypt_initialized = 0;
 
 static int alloc_key(struct ssh_cipher_struct *cipher) {
@@ -558,6 +586,210 @@ static void des3_decrypt(struct ssh_cipher_struct *cipher, void *in,
   gcry_cipher_decrypt(cipher->key[0], out, len, in, len);
 }
 
+#ifdef HAVE_GCRYPT_CHACHA_POLY
+static void chacha20_cleanup(struct ssh_cipher_struct *cipher) {
+    struct chacha20_poly1305_keysched *ctx;
+
+    if (cipher->chacha20_schedule == NULL)
+        return;
+
+    ctx = cipher->chacha20_schedule;
+
+    if (ctx->initialized) {
+        gcry_cipher_close(ctx->main_hd);
+        gcry_cipher_close(ctx->header_hd);
+        gcry_mac_close(ctx->mac_hd);
+        ctx->initialized = 0;
+    }
+
+    SAFE_FREE(cipher->chacha20_schedule);
+}
+
+static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher,
+                                    void *key,
+                                    void *IV)
+{
+    struct chacha20_poly1305_keysched *ctx;
+    uint8_t *u8key = key;
+    (void)IV;
+
+    if (cipher->chacha20_schedule == NULL) {
+        ctx = malloc(sizeof *ctx);
+        if (ctx == NULL){
+            return -1;
+        }
+        memset(ctx, 0, sizeof *ctx);
+        cipher->chacha20_schedule = ctx;
+    } else {
+        ctx = cipher->chacha20_schedule;
+    }
+
+    if (!ctx->initialized) {
+        /* Open cipher/mac handles. */
+        if (gcry_cipher_open(&ctx->main_hd, GCRY_CIPHER_CHACHA20,
+                             GCRY_CIPHER_MODE_STREAM, 0)) {
+            SAFE_FREE(cipher->chacha20_schedule);
+            return -1;
+        }
+        if (gcry_cipher_open(&ctx->header_hd, GCRY_CIPHER_CHACHA20,
+                             GCRY_CIPHER_MODE_STREAM, 0)) {
+            gcry_cipher_close(ctx->main_hd);
+            SAFE_FREE(cipher->chacha20_schedule);
+            return -1;
+        }
+        if (gcry_mac_open(&ctx->mac_hd, GCRY_MAC_POLY1305, 0, NULL)) {
+            gcry_cipher_close(ctx->main_hd);
+            gcry_cipher_close(ctx->header_hd);
+            SAFE_FREE(cipher->chacha20_schedule);
+            return -1;
+        }
+
+        ctx->initialized = 1;
+    }
+
+    if (gcry_cipher_setkey(ctx->main_hd, u8key, CHACHA20_KEYLEN) ||
+          gcry_cipher_setkey(ctx->header_hd, u8key + CHACHA20_KEYLEN,
+                             CHACHA20_KEYLEN)) {
+        chacha20_cleanup(cipher);
+        return -1;
+    }
+
+    return 0;
+}
+
+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;
+    struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule;
+    uint8_t poly_key[CHACHA20_BLOCKSIZE];
+    size_t taglen = POLY1305_TAGLEN;
+
+    seq = htonll(seq);
+    /* step 1, prepare the poly1305 key */
+    if (gcry_cipher_setiv(ctx->main_hd, (uint8_t *)&seq, sizeof(seq)))
+        goto out;
+    /* Output full ChaCha block so that counter increases by one for
+     * payload encryption step. */
+    if (gcry_cipher_encrypt(ctx->main_hd,
+                            poly_key,
+                            sizeof(poly_key),
+                            zero_block,
+                            sizeof(zero_block)))
+        goto out;
+    if (gcry_mac_setkey(ctx->mac_hd, poly_key, POLY1305_KEYLEN))
+        goto out;
+
+    /* step 2, encrypt length field */
+    if (gcry_cipher_setiv(ctx->header_hd, (uint8_t *)&seq, sizeof(seq)))
+        goto out;
+    if (gcry_cipher_encrypt(ctx->header_hd,
+                            (uint8_t *)&out_packet->length,
+                            sizeof(uint32_t),
+                            (uint8_t *)&in_packet->length,
+                            sizeof(uint32_t)))
+        goto out;
+
+    /* step 3, encrypt packet payload (main_hd counter == 1) */
+    if (gcry_cipher_encrypt(ctx->main_hd,
+                            out_packet->payload,
+                            len - sizeof(uint32_t),
+                            in_packet->payload,
+                            len - sizeof(uint32_t)))
+        goto out;
+
+    /* step 4, compute the MAC */
+    if (gcry_mac_write(ctx->mac_hd, (uint8_t *)out_packet, len))
+        goto out;
+    if (gcry_mac_read(ctx->mac_hd, tag, &taglen))
+        goto out;
+
+out:
+    explicit_bzero(poly_key, sizeof(poly_key));
+}
+
+static int chacha20_poly1305_aead_decrypt_length(
+        struct ssh_cipher_struct *cipher,
+        void *in,
+        uint8_t *out,
+        size_t len,
+        uint64_t seq)
+{
+    struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule;
+
+    if (len < sizeof(uint32_t)) {
+        return SSH_ERROR;
+    }
+    seq = htonll(seq);
+
+    if (gcry_cipher_setiv(ctx->header_hd, (uint8_t *)&seq, sizeof(seq)))
+        return SSH_ERROR;
+    if (gcry_cipher_decrypt(ctx->header_hd,
+                            out,
+                            sizeof(uint32_t),
+                            in,
+                            sizeof(uint32_t)))
+        return SSH_ERROR;
+
+    return SSH_OK;
+}
+
+static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher,
+                                          void *complete_packet,
+                                          uint8_t *out,
+                                          size_t encrypted_size,
+                                          uint64_t seq)
+{
+    struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule;
+    uint8_t *mac = (uint8_t *)complete_packet + sizeof(uint32_t) +
+                   encrypted_size;
+    uint8_t poly_key[CHACHA20_BLOCKSIZE];
+    int ret = SSH_ERROR;
+
+    seq = htonll(seq);
+    /* step 1, prepare the poly1305 key */
+    if (gcry_cipher_setiv(ctx->main_hd, (uint8_t *)&seq, sizeof(seq)))
+        goto out;
+    /* Output full ChaCha block so that counter increases by one for
+     * decryption step. */
+    if (gcry_cipher_encrypt(ctx->main_hd,
+                            poly_key,
+                            sizeof(poly_key),
+                            zero_block,
+                            sizeof(zero_block)))
+        goto out;
+    if (gcry_mac_setkey(ctx->mac_hd, poly_key, POLY1305_KEYLEN))
+        goto out;
+
+    /* step 2, check MAC */
+    if (gcry_mac_write(ctx->mac_hd, (uint8_t *)complete_packet,
+                       encrypted_size + sizeof(uint32_t)))
+        goto out;
+    if (gcry_mac_verify(ctx->mac_hd, mac, POLY1305_TAGLEN)) {
+        SSH_LOG(SSH_LOG_PACKET,"poly1305 verify error");
+        goto out;
+    }
+
+    /* step 3, decrypt packet payload (main_hd counter == 1) */
+    if (gcry_cipher_decrypt(ctx->main_hd,
+                            out,
+                            encrypted_size,
+                            (uint8_t *)complete_packet + sizeof(uint32_t),
+                            encrypted_size))
+        goto out;
+
+    ret = SSH_OK;
+
+out:
+    explicit_bzero(poly_key, sizeof(poly_key));
+    return ret;
+}
+#endif /* HAVE_GCRYPT_CHACHA_POLY */
+
 /* the table of supported ciphers */
 static struct ssh_cipher_struct ssh_ciphertab[] = {
 #ifdef WITH_BLOWFISH_CIPHER
@@ -679,7 +911,23 @@ static struct ssh_cipher_struct ssh_ciphertab[] = {
     .decrypt     = des3_decrypt
   },
   {
+#ifdef HAVE_GCRYPT_CHACHA_POLY
+    .ciphertype      = SSH_AEAD_CHACHA20_POLY1305,
+    .name            = "chacha20-poly1305@xxxxxxxxxxx",
+    .blocksize       = 8,
+    .lenfield_blocksize = 4,
+    .keylen          = sizeof(struct chacha20_poly1305_keysched),
+    .keysize         = 2 * CHACHA20_KEYLEN * 8,
+    .tag_size        = POLY1305_TAGLEN,
+    .set_encrypt_key = chacha20_set_encrypt_key,
+    .set_decrypt_key = chacha20_set_encrypt_key,
+    .aead_encrypt    = chacha20_poly1305_aead_encrypt,
+    .aead_decrypt_length = chacha20_poly1305_aead_decrypt_length,
+    .aead_decrypt    = chacha20_poly1305_aead_decrypt,
+    .cleanup         = chacha20_cleanup
+#else
     .name = "chacha20-poly1305@xxxxxxxxxxx"
+#endif
   },
   {
     .name            = NULL,
@@ -759,6 +1007,8 @@ int ssh_crypto_init(void)
 {
     size_t i;
 
+    (void)i;
+
     if (libgcrypt_initialized) {
         return SSH_OK;
     }
@@ -776,6 +1026,7 @@ int ssh_crypto_init(void)
     /* Re-enable warning */
     gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
 
+#ifndef HAVE_GCRYPT_CHACHA_POLY
     for (i = 0; ssh_ciphertab[i].name != NULL; i++) {
         int cmp;
         cmp = strcmp(ssh_ciphertab[i].name, "chacha20-poly1305@xxxxxxxxxxx");
@@ -786,6 +1037,7 @@ int ssh_crypto_init(void)
             break;
         }
     }
+#endif
 
     libgcrypt_initialized = 1;
 
-- 
2.20.1


References:
[PATCH 1/2] tests: add crypto unittest for chacha20poly1305Jussi Kivilinna <jussi.kivilinna@xxxxxx>
Archive administrator: postmaster@lists.cynapses.org