File 0001-libgcrypt-Implement-OpenSSH-compatible-AES-GCM-ciphe.patch of Package libssh.11497

From 5790036a2305d5610ac55adb5382ea55d043998f Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@redhat.com>
Date: Mon, 1 Oct 2018 14:32:05 +0200
Subject: [PATCH] libgcrypt: Implement OpenSSH-compatible AES-GCM ciphers using
 libgcrypt

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
---
 include/libssh/crypto.h |   1 +
 src/kex.c               |   4 +-
 src/libgcrypt.c         | 201 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 205 insertions(+), 1 deletion(-)

diff --git a/include/libssh/crypto.h b/include/libssh/crypto.h
index fc375a4f..8777f0c9 100644
--- a/include/libssh/crypto.h
+++ b/include/libssh/crypto.h
@@ -142,6 +142,7 @@ struct ssh_cipher_struct {
     size_t keylen; /* length of the key structure */
 #ifdef HAVE_LIBGCRYPT
     gcry_cipher_hd_t *key;
+    unsigned char last_iv[AES_GCM_IVLEN];
 #elif defined HAVE_LIBCRYPTO
     struct ssh_3des_key_schedule *des3_key;
     struct ssh_aes_key_schedule *aes_key;
diff --git a/src/kex.c b/src/kex.c
index e0fd5680..382d88fb 100644
--- a/src/kex.c
+++ b/src/kex.c
@@ -40,7 +40,9 @@
 
 #ifdef HAVE_LIBGCRYPT
 # define BLOWFISH "blowfish-cbc,"
-# define AES "aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc,"
+# define AES "aes256-gcm@openssh.com,aes128-gcm@openssh.com," \
+             "aes256-ctr,aes192-ctr,aes128-ctr," \
+             "aes256-cbc,aes192-cbc,aes128-cbc,"
 # define DES "3des-cbc"
 # define DES_SUPPORTED "3des-cbc"
 
diff --git a/src/libgcrypt.c b/src/libgcrypt.c
index f004ffe2..7160bb1c 100644
--- a/src/libgcrypt.c
+++ b/src/libgcrypt.c
@@ -353,6 +353,8 @@ static int aes_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) {
     }
     if(strstr(cipher->name,"-ctr"))
       mode=GCRY_CIPHER_MODE_CTR;
+    if (strstr(cipher->name, "-gcm"))
+      mode = GCRY_CIPHER_MODE_GCM;
     switch (cipher->keysize) {
       case 128:
         if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_AES128,
@@ -386,6 +388,11 @@ static int aes_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) {
         SAFE_FREE(cipher->key);
         return -1;
       }
+    } else if (mode == GCRY_CIPHER_MODE_GCM) {
+      /* Store the IV so we can handle the packet counter increments later
+       * The IV is passed to the cipher context later.
+       */
+      memcpy(cipher->last_iv, IV, AES_GCM_IVLEN);
     } else {
       if(gcry_cipher_setctr(cipher->key[0],IV,16)){
         SAFE_FREE(cipher->key);
@@ -407,6 +414,172 @@ static void aes_decrypt(struct ssh_cipher_struct *cipher, void *in, void *out,
   gcry_cipher_decrypt(cipher->key[0], out, len, in, len);
 }
 
+static int
+aes_aead_get_length(struct ssh_cipher_struct *cipher,
+                    void *in,
+                    uint8_t *out,
+                    size_t len,
+                    uint64_t seq)
+{
+    (void)seq;
+
+    /* The length is not encrypted: Copy it to the result buffer */
+    memcpy(out, in, len);
+
+    return SSH_OK;
+}
+
+/* Increment 64b integer in network byte order */
+static void
+uint64_inc(unsigned char *counter)
+{
+    int i;
+
+    for (i = 7; i >= 0; i--) {
+        counter[i]++;
+        if (counter[i])
+          return;
+    }
+}
+
+static void
+aes_gcm_encrypt(struct ssh_cipher_struct *cipher,
+                void *in,
+                void *out,
+                size_t len,
+                uint8_t *tag,
+                uint64_t seq)
+{
+    gpg_error_t err;
+    size_t aadlen, authlen;
+
+    (void)seq;
+
+    aadlen = cipher->lenfield_blocksize;
+    authlen = cipher->tag_size;
+
+    /* increment IV */
+    err = gcry_cipher_setiv(cipher->key[0],
+                            cipher->last_iv,
+                            AES_GCM_IVLEN);
+    /* This actualy does not increment the packet counter for the
+     * current encryption operation, but for the next one. The first
+     * operation needs to be completed with the derived IV.
+     *
+     * The IV buffer has the following structure:
+     * [ 4B static IV ][ 8B packet counter ][ 4B block counter ]
+     */
+    uint64_inc(cipher->last_iv + 4);
+    if (err) {
+        SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s",
+                gpg_strerror(err));
+        return;
+    }
+
+    /* Pass the authenticated data (packet_length) */
+    err = gcry_cipher_authenticate(cipher->key[0], in, aadlen);
+    if (err) {
+        SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_authenticate failed: %s",
+                gpg_strerror(err));
+        return;
+    }
+    memcpy(out, in, aadlen);
+
+    /* Encrypt the rest of the data */
+    err = gcry_cipher_encrypt(cipher->key[0],
+                              (unsigned char *)out + aadlen,
+                              len - aadlen,
+                              (unsigned char *)in + aadlen,
+                              len - aadlen);
+    if (err) {
+        SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s",
+                gpg_strerror(err));
+        return;
+    }
+
+    /* Calculate the tag */
+    err = gcry_cipher_gettag(cipher->key[0],
+                             (void *)tag,
+                             authlen);
+    if (err) {
+        SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_gettag failed: %s",
+                gpg_strerror(err));
+        return;
+    }
+}
+
+static int
+aes_gcm_decrypt(struct ssh_cipher_struct *cipher,
+                void *complete_packet,
+                uint8_t *out,
+                size_t encrypted_size,
+                uint64_t seq)
+{
+    gpg_error_t err;
+    size_t aadlen, authlen;
+
+    (void)seq;
+
+    aadlen = cipher->lenfield_blocksize;
+    authlen = cipher->tag_size;
+
+    /* increment IV */
+    err = gcry_cipher_setiv(cipher->key[0],
+                            cipher->last_iv,
+                            AES_GCM_IVLEN);
+    /* This actualy does not increment the packet counter for the
+     * current encryption operation, but for the next one. The first
+     * operation needs to be completed with the derived IV.
+     *
+     * The IV buffer has the following structure:
+     * [ 4B static IV ][ 8B packet counter ][ 4B block counter ]
+     */
+    uint64_inc(cipher->last_iv + 4);
+    if (err) {
+        SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s",
+                gpg_strerror(err));
+        return SSH_ERROR;
+    }
+
+    /* Pass the authenticated data (packet_length) */
+    err = gcry_cipher_authenticate(cipher->key[0],
+                                   complete_packet,
+                                   aadlen);
+    if (err) {
+        SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_authenticate failed: %s",
+                gpg_strerror(err));
+        return SSH_ERROR;
+    }
+    /* Do not copy the length to the target buffer, because it is already processed */
+    //memcpy(out, complete_packet, aadlen);
+
+    /* Encrypt the rest of the data */
+    err = gcry_cipher_decrypt(cipher->key[0],
+                              out,
+                              encrypted_size,
+                              (unsigned char *)complete_packet + aadlen,
+                              encrypted_size);
+    if (err) {
+        SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_decrypt failed: %s",
+                gpg_strerror(err));
+        return SSH_ERROR;
+    }
+
+    /* Check the tag */
+    err = gcry_cipher_checktag(cipher->key[0],
+                               (unsigned char *)complete_packet + aadlen + encrypted_size,
+                               authlen);
+    if (gpg_err_code(err) == GPG_ERR_CHECKSUM) {
+        SSH_LOG(SSH_LOG_WARNING, "The authentication tag does not match");
+        return SSH_ERROR;
+    } else if (err != GPG_ERR_NO_ERROR) {
+        SSH_LOG(SSH_LOG_WARNING, "General error while decryption: %s",
+                gpg_strerror(err));
+        return SSH_ERROR;
+    }
+    return SSH_OK;
+}
+
 static int des3_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) {
   if (cipher->key == NULL) {
     if (alloc_key(cipher) < 0) {
@@ -519,6 +692,34 @@ static struct ssh_cipher_struct ssh_ciphertab[] = {
     .encrypt     = aes_encrypt,
     .decrypt     = aes_decrypt
   },
+  {
+    .name            = "aes128-gcm@openssh.com",
+    .blocksize       = 16,
+    .lenfield_blocksize = 4, /* not encrypted, but authenticated */
+    .keylen          = sizeof(gcry_cipher_hd_t),
+    .key             = NULL,
+    .keysize         = 128,
+    .tag_size        = AES_GCM_TAGLEN,
+    .set_encrypt_key = aes_set_key,
+    .set_decrypt_key = aes_set_key,
+    .aead_encrypt    = aes_gcm_encrypt,
+    .aead_decrypt_length = aes_aead_get_length,
+    .aead_decrypt    = aes_gcm_decrypt,
+  },
+  {
+    .name            = "aes256-gcm@openssh.com",
+    .blocksize       = 16,
+    .lenfield_blocksize = 4, /* not encrypted, but authenticated */
+    .keylen          = sizeof(gcry_cipher_hd_t),
+    .key             = NULL,
+    .keysize         = 256,
+    .tag_size        = AES_GCM_TAGLEN,
+    .set_encrypt_key = aes_set_key,
+    .set_decrypt_key = aes_set_key,
+    .aead_encrypt    = aes_gcm_encrypt,
+    .aead_decrypt_length = aes_aead_get_length,
+    .aead_decrypt    = aes_gcm_decrypt,
+  },
   {
     .name            = "3des-cbc",
     .blocksize       = 8,
-- 
2.21.0

openSUSE Build Service is sponsored by