From 2aa8ec70177b976fac8671eb6d8ba3da3840e955 Mon Sep 17 00:00:00 2001
From: Dirkjan Bussink <d.bussink@gmail.com>
Date: Thu, 10 Apr 2014 13:58:16 +0200
Subject: [PATCH] Add support for SHA2 algorithms

This patch adds support for both hmac-sha2-256 and hmac-sha2-512 HMAC
algorithms in libssh. This makes the HMAC algorithm configurable in the
ssh_crypto_struct so it can be set to the appropriate algorithm.

This also fixes the stretching algorithm for the generated shared hmac
secret to work for all types and not just a special for the in_cipher
and out_cipher keys.

In a few other places there were hardcoded assumptions about sha1 always
being 20 bytes in length, they now use the proper length based on the
selected HMAC algorithm.
---
 include/libssh/crypto.h           |   6 +-
 include/libssh/libcrypto.h        |  13 +++++
 include/libssh/libgcrypt.h        |   8 ++-
 include/libssh/libssh.h           |   4 ++
 include/libssh/packet.h           |   7 ++-
 include/libssh/wrapper.h          |  27 +++++++++
 src/dh.c                          | 112 ++++++++++++++++++--------------------
 src/kex.c                         |   8 +--
 src/libcrypto.c                   |  69 +++++++++++++++++++++++
 src/libgcrypt.c                   |  74 ++++++++++++++++++++++++-
 src/options.c                     |  20 +++++++
 src/packet.c                      |  21 ++++---
 src/packet_crypt.c                |  16 +++---
 src/session.c                     |  32 ++++++++++-
 src/wrapper.c                     | 112 ++++++++++++++++++++++++++++++++++++++
 tests/client/torture_algorithms.c |  40 +++++++++++---
 16 files changed, 473 insertions(+), 96 deletions(-)

diff --git a/include/libssh/crypto.h b/include/libssh/crypto.h
index eaff2ff..5656180 100644
--- a/include/libssh/crypto.h
+++ b/include/libssh/crypto.h
@@ -46,6 +46,8 @@
 #include "libssh/kex.h"
 #include "libssh/curve25519.h"
 
+#define DIGEST_MAX_LEN 64
+
 enum ssh_key_exchange_e {
   /* diffie-hellman-group1-sha1 */
   SSH_KEX_DH_GROUP1_SHA1=1,
@@ -79,8 +81,10 @@ struct ssh_crypto_struct {
     unsigned char *encryptkey;
     unsigned char *encryptMAC;
     unsigned char *decryptMAC;
-    unsigned char hmacbuf[EVP_MAX_MD_SIZE];
+    unsigned char hmacbuf[DIGEST_MAX_LEN];
     struct ssh_cipher_struct *in_cipher, *out_cipher; /* the cipher structures/objects */
+    enum ssh_hmac_e in_hmac, out_hmac; /* the MAC algorithms used */
+
     ssh_string server_pubkey;
     const char *server_pubkey_type;
     int do_compress_out; /* idem */
diff --git a/include/libssh/libcrypto.h b/include/libssh/libcrypto.h
index 5cf2da2..c378388 100644
--- a/include/libssh/libcrypto.h
+++ b/include/libssh/libcrypto.h
@@ -36,6 +36,8 @@
 
 typedef SHA_CTX* SHACTX;
 typedef SHA256_CTX* SHA256CTX;
+typedef SHA512_CTX* SHA384CTX;
+typedef SHA512_CTX* SHA512CTX;
 typedef MD5_CTX*  MD5CTX;
 typedef HMAC_CTX* HMACCTX;
 #ifdef HAVE_ECC
@@ -45,6 +47,9 @@ typedef void *EVPCTX;
 #endif
 
 #define SHA_DIGEST_LEN SHA_DIGEST_LENGTH
+#define SHA256_DIGEST_LEN SHA256_DIGEST_LENGTH
+#define SHA384_DIGEST_LEN SHA384_DIGEST_LENGTH
+#define SHA512_DIGEST_LEN SHA512_DIGEST_LENGTH
 #ifdef MD5_DIGEST_LEN
     #undef MD5_DIGEST_LEN
 #endif
@@ -84,6 +89,14 @@ SHA256CTX sha256_init(void);
 void sha256_update(SHA256CTX c, const void *data, unsigned long len);
 void sha256_final(unsigned char *md, SHA256CTX c);
 
+SHA384CTX sha384_init(void);
+void sha384_update(SHA384CTX c, const void *data, unsigned long len);
+void sha384_final(unsigned char *md, SHA384CTX c);
+
+SHA512CTX sha512_init(void);
+void sha512_update(SHA512CTX c, const void *data, unsigned long len);
+void sha512_final(unsigned char *md, SHA512CTX c);
+
 struct ssh_cipher_struct *ssh_get_ciphertab(void);
 
 #endif /* HAVE_LIBCRYPTO */
diff --git a/include/libssh/libgcrypt.h b/include/libssh/libgcrypt.h
index c1844a5..8e52a68 100644
--- a/include/libssh/libgcrypt.h
+++ b/include/libssh/libgcrypt.h
@@ -27,6 +27,9 @@
 
 #include <gcrypt.h>
 typedef gcry_md_hd_t SHACTX;
+typedef gcry_md_hd_t SHA256CTX;
+typedef gcry_md_hd_t SHA384CTX;
+typedef gcry_md_hd_t SHA512CTX;
 typedef gcry_md_hd_t MD5CTX;
 typedef gcry_md_hd_t HMACCTX;
 typedef void *EVPCTX;
@@ -34,11 +37,14 @@ typedef void *EVPCTX;
 #define SHA_DIGEST_LEN SHA_DIGEST_LENGTH
 #define MD5_DIGEST_LEN 16
 #define SHA256_DIGEST_LENGTH 32
+#define SHA256_DIGEST_LEN SHA256_DIGEST_LENGTH
 #define SHA384_DIGEST_LENGTH 48
+#define SHA384_DIGEST_LEN SHA384_DIGEST_LENGTH
 #define SHA512_DIGEST_LENGTH 64
+#define SHA512_DIGEST_LEN SHA512_DIGEST_LENGTH
 
 #ifndef EVP_MAX_MD_SIZE
-#define EVP_MAX_MD_SIZE 36
+#define EVP_MAX_MD_SIZE 64
 #endif
 
 #define EVP_DIGEST_LEN EVP_MAX_MD_SIZE
diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h
index a166d05..c82a086 100644
--- a/include/libssh/libssh.h
+++ b/include/libssh/libssh.h
@@ -342,6 +342,8 @@ enum ssh_options_e {
   SSH_OPTIONS_GSSAPI_SERVER_IDENTITY,
   SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY,
   SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS,
+  SSH_OPTIONS_HMAC_C_S,
+  SSH_OPTIONS_HMAC_S_C,
 };
 
 enum {
@@ -657,6 +659,8 @@ LIBSSH_API const char* ssh_get_clientbanner(ssh_session session);
 LIBSSH_API const char* ssh_get_serverbanner(ssh_session session);
 LIBSSH_API const char* ssh_get_cipher_in(ssh_session session);
 LIBSSH_API const char* ssh_get_cipher_out(ssh_session session);
+LIBSSH_API const char* ssh_get_hmac_in(ssh_session session);
+LIBSSH_API const char* ssh_get_hmac_out(ssh_session session);
 
 #ifndef LIBSSH_LEGACY_0_4
 #include "libssh/legacy.h"
diff --git a/include/libssh/packet.h b/include/libssh/packet.h
index 513eaa8..758e586 100644
--- a/include/libssh/packet.h
+++ b/include/libssh/packet.h
@@ -21,6 +21,8 @@
 #ifndef PACKET_H_
 #define PACKET_H_
 
+#include "libssh/wrapper.h"
+
 struct ssh_socket_struct;
 
 /* this structure should go someday */
@@ -80,8 +82,9 @@ uint32_t packet_decrypt_len(ssh_session session, char *crypted);
 int packet_decrypt(ssh_session session, void *packet, unsigned int len);
 unsigned char *packet_encrypt(ssh_session session,
                               void *packet,
-                              unsigned int len);
+                              unsigned int len,
+                              enum ssh_hmac_e type);
 int packet_hmac_verify(ssh_session session,ssh_buffer buffer,
-                       unsigned char *mac);
+                       unsigned char *mac, enum ssh_hmac_e type);
 
 #endif /* PACKET_H_ */
diff --git a/include/libssh/wrapper.h b/include/libssh/wrapper.h
index e8ff32c..d19d097 100644
--- a/include/libssh/wrapper.h
+++ b/include/libssh/wrapper.h
@@ -34,6 +34,9 @@ enum ssh_mac_e {
 
 enum ssh_hmac_e {
   SSH_HMAC_SHA1 = 1,
+  SSH_HMAC_SHA256,
+  SSH_HMAC_SHA384,
+  SSH_HMAC_SHA512,
   SSH_HMAC_MD5
 };
 
@@ -42,16 +45,36 @@ enum ssh_des_e {
   SSH_DES
 };
 
+struct ssh_hmac_struct {
+  const char* name;
+  enum ssh_hmac_e hmac_type;
+};
+
 typedef struct ssh_mac_ctx_struct *ssh_mac_ctx;
 MD5CTX md5_init(void);
 void md5_update(MD5CTX c, const void *data, unsigned long len);
 void md5_final(unsigned char *md,MD5CTX c);
+
 SHACTX sha1_init(void);
 void sha1_update(SHACTX c, const void *data, unsigned long len);
 void sha1_final(unsigned char *md,SHACTX c);
 void sha1(unsigned char *digest,int len,unsigned char *hash);
+
+SHA256CTX sha256_init(void);
+void sha256_update(SHA256CTX c, const void *data, unsigned long len);
+void sha256_final(unsigned char *md,SHA256CTX c);
 void sha256(unsigned char *digest, int len, unsigned char *hash);
 
+SHA384CTX sha384_init(void);
+void sha384_update(SHA384CTX c, const void *data, unsigned long len);
+void sha384_final(unsigned char *md,SHA384CTX c);
+void sha384(unsigned char *digest, int len, unsigned char *hash);
+
+SHA512CTX sha512_init(void);
+void sha512_update(SHA512CTX c, const void *data, unsigned long len);
+void sha512_final(unsigned char *md,SHA512CTX c);
+void sha512(unsigned char *digest, int len, unsigned char *hash);
+
 void evp(int nid, unsigned char *digest, int len, unsigned char *hash, unsigned int *hlen);
 EVPCTX evp_init(int nid);
 void evp_update(EVPCTX ctx, const void *data, unsigned long len);
@@ -64,6 +87,7 @@ void ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx);
 HMACCTX hmac_init(const void *key,int len, enum ssh_hmac_e type);
 void hmac_update(HMACCTX c, const void *data, unsigned long len);
 void hmac_final(HMACCTX ctx,unsigned char *hashmacbuf,unsigned int *len);
+size_t hmac_digest_len(enum ssh_hmac_e type);
 
 int crypt_set_algorithms(ssh_session session, enum ssh_des_e des_type);
 int crypt_set_algorithms_server(ssh_session session);
@@ -72,4 +96,7 @@ void crypto_free(struct ssh_crypto_struct *crypto);
 
 void ssh_reseed(void);
 
+struct ssh_hmac_struct *ssh_get_hmactab(void);
+const char *ssh_hmac_type_to_string(enum ssh_hmac_e hmac_type);
+
 #endif /* WRAPPER_H_ */
diff --git a/src/dh.c b/src/dh.c
index ca2c2bf..0dfd128 100644
--- a/src/dh.c
+++ b/src/dh.c
@@ -848,8 +848,10 @@ int hashbufin_add_cookie(ssh_session session, unsigned char *cookie) {
 }
 
 static int generate_one_key(ssh_string k,
-    struct ssh_crypto_struct *crypto, unsigned char *output, char letter) {
+    struct ssh_crypto_struct *crypto, unsigned char **output, char letter, size_t requested_size) {
   ssh_mac_ctx ctx;
+  unsigned char *tmp;
+  size_t size = crypto->digest_len;
   ctx=ssh_mac_ctx_init(crypto->mac_type);
 
   if (ctx == NULL) {
@@ -860,16 +862,33 @@ static int generate_one_key(ssh_string k,
   ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len);
   ssh_mac_update(ctx, &letter, 1);
   ssh_mac_update(ctx, crypto->session_id, crypto->digest_len);
-  ssh_mac_final(output, ctx);
+  ssh_mac_final(*output, ctx);
+
+  while(requested_size > size) {
+    tmp = realloc(*output, size + crypto->digest_len);
+    if (tmp == NULL) {
+      return -1;
+    }
+    *output = tmp;
+
+    ctx = ssh_mac_ctx_init(crypto->mac_type);
+    if (ctx == NULL) {
+      return -1;
+    }
+    ssh_mac_update(ctx, k, ssh_string_len(k) + 4);
+    ssh_mac_update(ctx, crypto->secret_hash,
+        crypto->digest_len);
+    ssh_mac_update(ctx, tmp, size);
+    ssh_mac_final(tmp + size, ctx);
+    size += crypto->digest_len;
+  }
 
   return 0;
 }
 
 int generate_session_keys(ssh_session session) {
   ssh_string k_string = NULL;
-  ssh_mac_ctx ctx = NULL;
   struct ssh_crypto_struct *crypto = session->next_crypto;
-  unsigned char *tmp;
   int rc = -1;
 
   k_string = make_bignum_string(crypto->k);
@@ -893,96 +912,71 @@ int generate_session_keys(ssh_session session) {
 
   /* IV */
   if (session->client) {
-    if (generate_one_key(k_string, crypto, crypto->encryptIV, 'A') < 0) {
+    rc = generate_one_key(k_string, crypto, &crypto->encryptIV, 'A', crypto->digest_len);
+    if (rc < 0) {
       goto error;
     }
-    if (generate_one_key(k_string, crypto, crypto->decryptIV, 'B') < 0) {
+    rc = generate_one_key(k_string, crypto, &crypto->decryptIV, 'B', crypto->digest_len);
+    if (rc < 0) {
       goto error;
     }
   } else {
-    if (generate_one_key(k_string, crypto, crypto->decryptIV, 'A') < 0) {
+    rc = generate_one_key(k_string, crypto, &crypto->decryptIV, 'A', crypto->digest_len);
+    if (rc < 0) {
       goto error;
     }
-    if (generate_one_key(k_string, crypto, crypto->encryptIV, 'B') < 0) {
+    rc = generate_one_key(k_string, crypto, &crypto->encryptIV, 'B', crypto->digest_len);
+    if (rc < 0) {
       goto error;
     }
   }
   if (session->client) {
-    if (generate_one_key(k_string, crypto, crypto->encryptkey, 'C') < 0) {
+    rc = generate_one_key(k_string, crypto, &crypto->encryptkey, 'C', crypto->out_cipher->keysize / 8);
+    if (rc < 0) {
       goto error;
     }
-    if (generate_one_key(k_string, crypto, crypto->decryptkey, 'D') < 0) {
+    rc = generate_one_key(k_string, crypto, &crypto->decryptkey, 'D', crypto->in_cipher->keysize / 8);
+    if (rc < 0) {
       goto error;
     }
   } else {
-    if (generate_one_key(k_string, crypto, crypto->decryptkey, 'C') < 0) {
-      goto error;
-    }
-    if (generate_one_key(k_string, crypto, crypto->encryptkey, 'D') < 0) {
+    rc = generate_one_key(k_string, crypto, &crypto->decryptkey, 'C', crypto->in_cipher->keysize / 8);
+    if (rc < 0) {
       goto error;
     }
-  }
-
-  /* some ciphers need more than DIGEST_LEN bytes of input key */
-  if (crypto->out_cipher->keysize > crypto->digest_len * 8) {
-      tmp = realloc(crypto->encryptkey, crypto->digest_len * 2);
-      if (tmp == NULL) {
-          goto error;
-      }
-      crypto->encryptkey = tmp;
-
-    ctx = ssh_mac_ctx_init(crypto->mac_type);
-    if (ctx == NULL) {
+    rc = generate_one_key(k_string, crypto, &crypto->encryptkey, 'D', crypto->out_cipher->keysize / 8);
+    if (rc < 0) {
       goto error;
     }
-    ssh_mac_update(ctx, k_string, ssh_string_len(k_string) + 4);
-    ssh_mac_update(ctx, crypto->secret_hash,
-        crypto->digest_len);
-    ssh_mac_update(ctx, crypto->encryptkey, crypto->digest_len);
-    ssh_mac_final(crypto->encryptkey + crypto->digest_len, ctx);
   }
 
-  if (crypto->in_cipher->keysize > crypto->digest_len * 8) {
-      tmp = realloc(crypto->decryptkey, crypto->digest_len *2);
-      if (tmp == NULL) {
-          goto error;
-      }
-      crypto->decryptkey = tmp;
-
-    if(crypto->decryptkey == NULL)
-      goto error;
-    ctx = ssh_mac_ctx_init(crypto->mac_type);
-    ssh_mac_update(ctx, k_string, ssh_string_len(k_string) + 4);
-    ssh_mac_update(ctx, crypto->secret_hash,
-        crypto->digest_len);
-    ssh_mac_update(ctx, crypto->decryptkey, crypto->digest_len);
-    ssh_mac_final(crypto->decryptkey + crypto->digest_len, ctx);
-  }
   if(session->client) {
-    if (generate_one_key(k_string, crypto, crypto->encryptMAC, 'E') < 0) {
+    rc = generate_one_key(k_string, crypto, &crypto->encryptMAC, 'E', hmac_digest_len(crypto->out_hmac));
+    if (rc < 0) {
       goto error;
     }
-    if (generate_one_key(k_string, crypto, crypto->decryptMAC, 'F') < 0) {
+    rc = generate_one_key(k_string, crypto, &crypto->decryptMAC, 'F', hmac_digest_len(crypto->in_hmac));
+    if (rc < 0) {
       goto error;
     }
   } else {
-    if (generate_one_key(k_string, crypto, crypto->decryptMAC, 'E') < 0) {
+    rc = generate_one_key(k_string, crypto, &crypto->decryptMAC, 'E', hmac_digest_len(crypto->in_hmac));
+    if (rc < 0) {
       goto error;
     }
-    if (generate_one_key(k_string, crypto, crypto->encryptMAC, 'F') < 0) {
+    rc = generate_one_key(k_string, crypto, &crypto->encryptMAC, 'F', hmac_digest_len(crypto->out_hmac));
+    if (rc < 0) {
       goto error;
     }
   }
 
 #ifdef DEBUG_CRYPTO
-  ssh_print_hexa("Encrypt IV", crypto->encryptIV, SHA_DIGEST_LEN);
-  ssh_print_hexa("Decrypt IV", crypto->decryptIV, SHA_DIGEST_LEN);
-  ssh_print_hexa("Encryption key", crypto->encryptkey,
-      crypto->out_cipher->keysize);
-  ssh_print_hexa("Decryption key", crypto->decryptkey,
-      crypto->in_cipher->keysize);
-  ssh_print_hexa("Encryption MAC", crypto->encryptMAC, SHA_DIGEST_LEN);
-  ssh_print_hexa("Decryption MAC", crypto->decryptMAC, 20);
+  ssh_print_hexa("Encrypt IV", crypto->encryptIV, crypto->digest_len);
+  ssh_print_hexa("Decrypt IV", crypto->decryptIV, crypto->digest_len);
+  ssh_print_hexa("Encryption key", crypto->encryptkey, crypto->out_cipher->keysize / 8);
+  ssh_print_hexa("Decryption key", crypto->decryptkey, crypto->in_cipher->keysize / 8);
+  ssh_print_hexa("Encryption MAC", crypto->encryptMAC, hmac_digest_len(crypto->out_hmac));
+  ssh_print_hexa("Decryption MAC", crypto->decryptMAC, hmac_digest_len(crypto->in_hmac));
 #endif
 
   rc = 0;
diff --git a/src/kex.c b/src/kex.c
index 4eb45d2..adf29cd 100644
--- a/src/kex.c
+++ b/src/kex.c
@@ -88,8 +88,8 @@ static const char *default_methods[] = {
   HOSTKEYS,
   AES BLOWFISH DES,
   AES BLOWFISH DES,
-  "hmac-sha1",
-  "hmac-sha1",
+  "hmac-sha1,hmac-sha2-256,hmac-sha2-512",
+  "hmac-sha1,hmac-sha2-256,hmac-sha2-512",
   "none",
   "none",
   "",
@@ -103,8 +103,8 @@ static const char *supported_methods[] = {
   HOSTKEYS,
   AES BLOWFISH DES,
   AES BLOWFISH DES,
-  "hmac-sha1",
-  "hmac-sha1",
+  "hmac-sha1,hmac-sha2-256,hmac-sha2-512",
+  "hmac-sha1,hmac-sha2-256,hmac-sha2-512",
   ZLIB,
   ZLIB,
   "",
diff --git a/src/libcrypto.c b/src/libcrypto.c
index d8cc795..84afbb0 100644
--- a/src/libcrypto.c
+++ b/src/libcrypto.c
@@ -65,6 +65,8 @@ struct ssh_mac_ctx_struct {
   union {
     SHACTX sha1_ctx;
     SHA256CTX sha256_ctx;
+    SHA384CTX sha384_ctx;
+    SHA512CTX sha512_ctx;
   } ctx;
 };
 
@@ -181,6 +183,52 @@ void sha256(unsigned char *digest, int len, unsigned char *hash) {
   SHA256(digest, len, hash);
 }
 
+SHA384CTX sha384_init(void){
+  SHA384CTX c = malloc(sizeof(*c));
+  if (c == NULL) {
+    return NULL;
+  }
+  SHA384_Init(c);
+
+  return c;
+}
+
+void sha384_update(SHA384CTX c, const void *data, unsigned long len){
+  SHA384_Update(c,data,len);
+}
+
+void sha384_final(unsigned char *md, SHA384CTX c) {
+  SHA384_Final(md, c);
+  SAFE_FREE(c);
+}
+
+void sha384(unsigned char *digest, int len, unsigned char *hash) {
+  SHA384(digest, len, hash);
+}
+
+SHA512CTX sha512_init(void){
+  SHA512CTX c = malloc(sizeof(*c));
+  if (c == NULL) {
+    return NULL;
+  }
+  SHA512_Init(c);
+
+  return c;
+}
+
+void sha512_update(SHA512CTX c, const void *data, unsigned long len){
+  SHA512_Update(c,data,len);
+}
+
+void sha512_final(unsigned char *md, SHA512CTX c) {
+  SHA512_Final(md, c);
+  SAFE_FREE(c);
+}
+
+void sha512(unsigned char *digest, int len, unsigned char *hash) {
+  SHA512(digest, len, hash);
+}
+
 MD5CTX md5_init(void) {
   MD5CTX c = malloc(sizeof(*c));
   if (c == NULL) {
@@ -212,7 +260,11 @@ ssh_mac_ctx ssh_mac_ctx_init(enum ssh_mac_e type){
       ctx->ctx.sha256_ctx = sha256_init();
       return ctx;
     case SSH_MAC_SHA384:
+      ctx->ctx.sha384_ctx = sha384_init();
+      return ctx;
     case SSH_MAC_SHA512:
+      ctx->ctx.sha512_ctx = sha512_init();
+      return ctx;
     default:
       SAFE_FREE(ctx);
       return NULL;
@@ -228,7 +280,11 @@ void ssh_mac_update(ssh_mac_ctx ctx, const void *data, unsigned long len) {
       sha256_update(ctx->ctx.sha256_ctx, data, len);
       break;
     case SSH_MAC_SHA384:
+      sha384_update(ctx->ctx.sha384_ctx, data, len);
+      break;
     case SSH_MAC_SHA512:
+      sha512_update(ctx->ctx.sha512_ctx, data, len);
+      break;
     default:
       break;
   }
@@ -243,7 +299,11 @@ void ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) {
       sha256_final(md,ctx->ctx.sha256_ctx);
       break;
     case SSH_MAC_SHA384:
+      sha384_final(md,ctx->ctx.sha384_ctx);
+      break;
     case SSH_MAC_SHA512:
+      sha512_final(md,ctx->ctx.sha512_ctx);
+      break;
     default:
       break;
   }
@@ -266,6 +326,15 @@ HMACCTX hmac_init(const void *key, int len, enum ssh_hmac_e type) {
     case SSH_HMAC_SHA1:
       HMAC_Init(ctx, key, len, EVP_sha1());
       break;
+    case SSH_HMAC_SHA256:
+      HMAC_Init(ctx, key, len, EVP_sha256());
+      break;
+    case SSH_HMAC_SHA384:
+      HMAC_Init(ctx, key, len, EVP_sha384());
+      break;
+    case SSH_HMAC_SHA512:
+      HMAC_Init(ctx, key, len, EVP_sha512());
+      break;
     case SSH_HMAC_MD5:
       HMAC_Init(ctx, key, len, EVP_md5());
       break;
diff --git a/src/libgcrypt.c b/src/libgcrypt.c
index 4617901..aabcfe7 100644
--- a/src/libgcrypt.c
+++ b/src/libgcrypt.c
@@ -69,10 +69,69 @@ void sha1(unsigned char *digest, int len, unsigned char *hash) {
   gcry_md_hash_buffer(GCRY_MD_SHA1, hash, digest, len);
 }
 
+SHA256CTX sha256_init(void) {
+  SHA256CTX ctx = NULL;
+  gcry_md_open(&ctx, GCRY_MD_SHA256, 0);
+
+  return ctx;
+}
+
+void sha256_update(SHACTX c, const void *data, unsigned long len) {
+  gcry_md_write(c, data, len);
+}
+
+void sha256_final(unsigned char *md, SHACTX c) {
+  gcry_md_final(c);
+  memcpy(md, gcry_md_read(c, 0), SHA256_DIGEST_LEN);
+  gcry_md_close(c);
+}
+
 void sha256(unsigned char *digest, int len, unsigned char *hash){
   gcry_md_hash_buffer(GCRY_MD_SHA256, hash, digest, len);
 }
 
+SHA384CTX sha384_init(void) {
+  SHA384CTX ctx = NULL;
+  gcry_md_open(&ctx, GCRY_MD_SHA384, 0);
+
+  return ctx;
+}
+
+void sha384_update(SHACTX c, const void *data, unsigned long len) {
+  gcry_md_write(c, data, len);
+}
+
+void sha384_final(unsigned char *md, SHACTX c) {
+  gcry_md_final(c);
+  memcpy(md, gcry_md_read(c, 0), SHA384_DIGEST_LEN);
+  gcry_md_close(c);
+}
+
+void sha384(unsigned char *digest, int len, unsigned char *hash) {
+  gcry_md_hash_buffer(GCRY_MD_SHA384, hash, digest, len);
+}
+
+SHA512CTX sha512_init(void) {
+  SHA512CTX ctx = NULL;
+  gcry_md_open(&ctx, GCRY_MD_SHA512, 0);
+
+  return ctx;
+}
+
+void sha512_update(SHACTX c, const void *data, unsigned long len) {
+  gcry_md_write(c, data, len);
+}
+
+void sha512_final(unsigned char *md, SHACTX c) {
+  gcry_md_final(c);
+  memcpy(md, gcry_md_read(c, 0), SHA512_DIGEST_LEN);
+  gcry_md_close(c);
+}
+
+void sha512(unsigned char *digest, int len, unsigned char *hash) {
+  gcry_md_hash_buffer(GCRY_MD_SHA512, hash, digest, len);
+}
+
 MD5CTX md5_init(void) {
   MD5CTX c = NULL;
   gcry_md_open(&c, GCRY_MD_MD5, 0);
@@ -124,13 +183,13 @@ void ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) {
       len=SHA_DIGEST_LEN;
       break;
     case SSH_MAC_SHA256:
-      len=SHA256_DIGEST_LENGTH;
+      len=SHA256_DIGEST_LEN;
       break;
     case SSH_MAC_SHA384:
-      len=SHA384_DIGEST_LENGTH;
+      len=SHA384_DIGEST_LEN;
       break;
     case SSH_MAC_SHA512:
-      len=SHA512_DIGEST_LENGTH;
+      len=SHA512_DIGEST_LEN;
       break;
   }
   gcry_md_final(ctx->ctx);
@@ -146,6 +205,15 @@ HMACCTX hmac_init(const void *key, int len, enum ssh_hmac_e type) {
     case SSH_HMAC_SHA1:
       gcry_md_open(&c, GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC);
       break;
+    case SSH_HMAC_SHA256:
+      gcry_md_open(&c, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
+      break;
+    case SSH_HMAC_SHA384:
+      gcry_md_open(&c, GCRY_MD_SHA384, GCRY_MD_FLAG_HMAC);
+      break;
+    case SSH_HMAC_SHA512:
+      gcry_md_open(&c, GCRY_MD_SHA512, GCRY_MD_FLAG_HMAC);
+      break;
     case SSH_HMAC_MD5:
       gcry_md_open(&c, GCRY_MD_MD5, GCRY_MD_FLAG_HMAC);
       break;
diff --git a/src/options.c b/src/options.c
index 25442e3..f7f2455 100644
--- a/src/options.c
+++ b/src/options.c
@@ -716,6 +716,26 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type,
                     return -1;
             }
             break;
+        case SSH_OPTIONS_HMAC_C_S:
+            v = value;
+            if (v == NULL || v[0] == '\0') {
+                ssh_set_error_invalid(session);
+                return -1;
+            } else {
+                if (ssh_options_set_algo(session, SSH_MAC_C_S, v) < 0)
+                    return -1;
+            }
+            break;
+         case SSH_OPTIONS_HMAC_S_C:
+            v = value;
+            if (v == NULL || v[0] == '\0') {
+                ssh_set_error_invalid(session);
+                return -1;
+            } else {
+                if (ssh_options_set_algo(session, SSH_MAC_S_C, v) < 0)
+                    return -1;
+            }
+            break;
         case SSH_OPTIONS_COMPRESSION_C_S:
             v = value;
             if (v == NULL || v[0] == '\0') {
diff --git a/src/packet.c b/src/packet.c
index bac4a0f..fb35d0f 100644
--- a/src/packet.c
+++ b/src/packet.c
@@ -48,8 +48,6 @@
 #include "libssh/auth.h"
 #include "libssh/gssapi.h"
 
-#define MACSIZE SHA_DIGEST_LEN
-
 static ssh_packet_callback default_packet_handlers[]= {
   ssh_packet_disconnect_callback,          // SSH2_MSG_DISCONNECT                 1
   ssh_packet_ignore_callback,              // SSH2_MSG_IGNORE	                    2
@@ -146,9 +144,9 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user)
     ssh_session session= (ssh_session) user;
     unsigned int blocksize = (session->current_crypto ?
                               session->current_crypto->in_cipher->blocksize : 8);
-    int current_macsize = session->current_crypto ? MACSIZE : 0;
-    unsigned char mac[30] = {0};
+    unsigned char mac[DIGEST_MAX_LEN] = {0};
     char buffer[16] = {0};
+    size_t current_macsize = 0;
     const uint8_t *packet;
     int to_be_read;
     int rc;
@@ -156,6 +154,10 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user)
     uint8_t padding;
     size_t processed = 0; /* number of byte processed from the callback */
 
+    if(session->current_crypto != NULL) {
+      current_macsize = hmac_digest_len(session->current_crypto->in_hmac);
+    }
+
     if (data == NULL) {
         goto error;
     }
@@ -267,9 +269,9 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user)
 
                 /* copy the last part from the incoming buffer */
                 packet = ((uint8_t *)data) + processed;
-                memcpy(mac, packet, MACSIZE);
+                memcpy(mac, packet, current_macsize);
 
-                rc = packet_hmac_verify(session, session->in_buffer, mac);
+                rc = packet_hmac_verify(session, session->in_buffer, mac, session->current_crypto->in_hmac);
                 if (rc < 0) {
                     ssh_set_error(session, SSH_FATAL, "HMAC error");
                     goto error;
@@ -506,6 +508,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);
+  enum ssh_hmac_e hmac_type = (session->current_crypto ?
+      session->current_crypto->out_hmac : session->next_crypto->out_hmac);
   uint32_t currentlen = buffer_get_rest_len(session->out_buffer);
   unsigned char *hmac = NULL;
   char padstring[32] = { 0 };
@@ -556,9 +560,10 @@ static int packet_send2(ssh_session session) {
   }
 #endif
   hmac = packet_encrypt(session, buffer_get_rest(session->out_buffer),
-      buffer_get_rest_len(session->out_buffer));
+      buffer_get_rest_len(session->out_buffer), hmac_type);
   if (hmac) {
-    if (ssh_buffer_add_data(session->out_buffer, hmac, 20) < 0) {
+    rc = ssh_buffer_add_data(session->out_buffer, hmac, hmac_digest_len(hmac_type));
+    if (rc < 0) {
       goto error;
     }
   }
diff --git a/src/packet_crypt.c b/src/packet_crypt.c
index cb73e41..d1fd8dc 100644
--- a/src/packet_crypt.c
+++ b/src/packet_crypt.c
@@ -86,7 +86,7 @@ int packet_decrypt(ssh_session session, void *data,uint32_t len) {
   return 0;
 }
 
-unsigned char *packet_encrypt(ssh_session session, void *data, uint32_t len) {
+unsigned char *packet_encrypt(ssh_session session, void *data, uint32_t len, enum ssh_hmac_e type) {
   struct ssh_cipher_struct *crypto = NULL;
   HMACCTX ctx = NULL;
   char *out = NULL;
@@ -117,7 +117,7 @@ unsigned char *packet_encrypt(ssh_session session, void *data, uint32_t len) {
   }
 
   if (session->version == 2) {
-    ctx = hmac_init(session->current_crypto->encryptMAC,20,SSH_HMAC_SHA1);
+    ctx = hmac_init(session->current_crypto->encryptMAC, hmac_digest_len(type), type);
     if (ctx == NULL) {
       SAFE_FREE(out);
       return NULL;
@@ -126,11 +126,11 @@ unsigned char *packet_encrypt(ssh_session session, void *data, uint32_t len) {
     hmac_update(ctx,data,len);
     hmac_final(ctx,session->current_crypto->hmacbuf,&finallen);
 #ifdef DEBUG_CRYPTO
-    ssh_print_hexa("mac: ",data,len);
-    if (finallen != 20) {
+    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, 20);
+    ssh_print_hexa("Packet hmac", session->current_crypto->hmacbuf, hmac_digest_len(type));
 #endif
   }
 
@@ -160,13 +160,13 @@ unsigned char *packet_encrypt(ssh_session session, void *data, uint32_t len) {
  *                      occurred.
  */
 int packet_hmac_verify(ssh_session session, ssh_buffer buffer,
-    unsigned char *mac) {
-  unsigned char hmacbuf[EVP_MAX_MD_SIZE] = {0};
+    unsigned char *mac, enum ssh_hmac_e type) {
+  unsigned char hmacbuf[DIGEST_MAX_LEN] = {0};
   HMACCTX ctx;
   unsigned int len;
   uint32_t seq;
 
-  ctx = hmac_init(session->current_crypto->decryptMAC, 20, SSH_HMAC_SHA1);
+  ctx = hmac_init(session->current_crypto->decryptMAC, hmac_digest_len(type), type);
   if (ctx == NULL) {
     return -1;
   }
diff --git a/src/session.c b/src/session.c
index 39fc597..f426c6d 100644
--- a/src/session.c
+++ b/src/session.c
@@ -310,7 +310,7 @@ const char* ssh_get_serverbanner(ssh_session session) {
 }
 
 /**
- * @brief get the name of the input for the given session.
+ * @brief get the name of the input cipher for the given session.
  *
  * @param[in] session The SSH session.
  *
@@ -342,6 +342,36 @@ const char* ssh_get_cipher_out(ssh_session session) {
 }
 
 /**
+ * @brief get the name of the input HMAC algorithm for the given session.
+ *
+ * @param[in] session The SSH session.
+ *
+ * @return Returns HMAC algorithm name or NULL if unknown.
+ */
+const char* ssh_get_hmac_in(ssh_session session) {
+    if ((session != NULL) &&
+        (session->current_crypto != NULL)) {
+        return ssh_hmac_type_to_string(session->current_crypto->in_hmac);
+    }
+    return NULL;
+}
+
+/**
+ * @brief get the name of the output HMAC algorithm for the given session.
+ *
+ * @param[in] session The SSH session.
+ *
+ * @return Returns HMAC algorithm name or NULL if unknown.
+ */
+const char* ssh_get_hmac_out(ssh_session session) {
+    if ((session != NULL) &&
+        (session->current_crypto != NULL)) {
+        return ssh_hmac_type_to_string(session->current_crypto->out_hmac);
+    }
+    return NULL;
+}
+
+/**
  * @brief Disconnect impolitely from a remote host by closing the socket.
  *
  * Suitable if you forked and want to destroy this session.
diff --git a/src/wrapper.c b/src/wrapper.c
index 94175d0..e12da93 100644
--- a/src/wrapper.c
+++ b/src/wrapper.c
@@ -48,6 +48,47 @@
 #include "libssh/wrapper.h"
 #include "libssh/pki.h"
 
+
+static struct ssh_hmac_struct ssh_hmac_tab[] = {
+  { "hmac-sha1",     SSH_HMAC_SHA1 },
+  { "hmac-sha2-256", SSH_HMAC_SHA256 },
+  { "hmac-sha2-384", SSH_HMAC_SHA384 },
+  { "hmac-sha2-512", SSH_HMAC_SHA512 },
+  { "hmac-md5",      SSH_HMAC_MD5 },
+  { NULL,            0}
+};
+
+struct ssh_hmac_struct *ssh_get_hmactab(void) {
+  return ssh_hmac_tab;
+}
+
+size_t hmac_digest_len(enum ssh_hmac_e type) {
+  switch(type) {
+    case SSH_HMAC_SHA1:
+      return SHA_DIGEST_LEN;
+    case SSH_HMAC_SHA256:
+      return SHA256_DIGEST_LEN;
+    case SSH_HMAC_SHA384:
+      return SHA384_DIGEST_LEN;
+    case SSH_HMAC_SHA512:
+      return SHA512_DIGEST_LEN;
+    case SSH_HMAC_MD5:
+      return MD5_DIGEST_LEN;
+    default:
+      return 0;
+  }
+}
+
+const char *ssh_hmac_type_to_string(enum ssh_hmac_e hmac_type)
+{
+  int i = 0;
+  struct ssh_hmac_struct *ssh_hmactab = ssh_get_hmactab();
+  while (ssh_hmactab[i].name && (ssh_hmactab[i].hmac_type != hmac_type)) {
+    i++;
+  }
+  return ssh_hmactab[i].name;
+}
+
 /* it allocates a new cipher structure based on its offset into the global table */
 static struct ssh_cipher_struct *cipher_new(int offset) {
   struct ssh_cipher_struct *cipher = NULL;
@@ -167,6 +208,7 @@ static int crypt_set_algorithms2(ssh_session session){
   const char *wanted;
   int i = 0;
   struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab();
+  struct ssh_hmac_struct *ssh_hmactab=ssh_get_hmactab();
 
   /* we must scan the kex entries to find crypto algorithms and set their appropriate structure */
   /* out */
@@ -190,6 +232,24 @@ 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];
+  while (ssh_hmactab[i].name && strcmp(wanted, ssh_hmactab[i].name)) {
+    i++;
+  }
+
+  if (ssh_hmactab[i].name == NULL) {
+    ssh_set_error(session, SSH_FATAL,
+        "crypt_set_algorithms2: no hmac algorithm function found for %s",
+        wanted);
+      return SSH_ERROR;
+  }
+  SSH_LOG(SSH_LOG_PACKET, "Set HMAC output algorithm to %s", wanted);
+
+  session->next_crypto->out_hmac = ssh_hmactab[i].hmac_type;
+  i = 0;
+
   /* in */
   wanted = session->next_crypto->kex_methods[SSH_CRYPT_S_C];
   while (ssh_ciphertab[i].name && strcmp(wanted, ssh_ciphertab[i].name)) {
@@ -210,6 +270,23 @@ static int crypt_set_algorithms2(ssh_session session){
       return SSH_ERROR;
   }
 
+  /* we must scan the kex entries to find hmac algorithms and set their appropriate structure */
+  wanted = session->next_crypto->kex_methods[SSH_MAC_S_C];
+  while (ssh_hmactab[i].name && strcmp(wanted, ssh_hmactab[i].name)) {
+    i++;
+  }
+
+  if (ssh_hmactab[i].name == NULL) {
+    ssh_set_error(session, SSH_FATAL,
+        "crypt_set_algorithms2: no hmac algorithm function found for %s",
+        wanted);
+      return SSH_ERROR;
+  }
+  SSH_LOG(SSH_LOG_PACKET, "Set HMAC output algorithm to %s", wanted);
+
+  session->next_crypto->in_hmac = ssh_hmactab[i].hmac_type;
+  i = 0;
+
   /* compression */
   if (strcmp(session->next_crypto->kex_methods[SSH_COMP_C_S], "zlib") == 0) {
     session->next_crypto->do_compress_out = 1;
@@ -267,6 +344,7 @@ int crypt_set_algorithms_server(ssh_session session){
     char *method = NULL;
     int i = 0;
     struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab();
+    struct ssh_hmac_struct   *ssh_hmactab=ssh_get_hmactab();
 
     if (session == NULL) {
         return SSH_ERROR;
@@ -309,6 +387,40 @@ int crypt_set_algorithms_server(ssh_session session){
         ssh_set_error_oom(session);
         return SSH_ERROR;
     }
+    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++;
+    }
+
+    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 input algorithm to %s", method);
+
+    session->next_crypto->in_hmac = ssh_hmactab[i].hmac_type;
+    i=0;
 
     /* compression */
     method = session->next_crypto->kex_methods[SSH_COMP_C_S];
diff --git a/tests/client/torture_algorithms.c b/tests/client/torture_algorithms.c
index 180efb7..757355c 100644
--- a/tests/client/torture_algorithms.c
+++ b/tests/client/torture_algorithms.c
@@ -37,7 +37,7 @@ static void teardown(void **state) {
     ssh_free(*state);
 }
 
-static void test_algorithm(ssh_session session, const char *algo) {
+static void test_algorithm(ssh_session session, const char *algo, const char *hmac) {
     int rc;
 
     rc = ssh_options_set(session, SSH_OPTIONS_HOST, "localhost");
@@ -49,6 +49,12 @@ static void test_algorithm(ssh_session session, const char *algo) {
     rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, algo);
     assert_true(rc == SSH_OK);
 
+    rc = ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, hmac);
+    assert_true(rc == SSH_OK);
+
+    rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, hmac);
+    assert_true(rc == SSH_OK);
+
     rc = ssh_connect(session);
     assert_true(rc == SSH_OK);
 
@@ -62,35 +68,51 @@ static void test_algorithm(ssh_session session, const char *algo) {
 }
 
 static void torture_algorithms_aes128_cbc(void **state) {
-    test_algorithm(*state, "aes128-cbc");
+    test_algorithm(*state, "aes128-cbc", "hmac-sha1");
+    test_algorithm(*state, "aes128-cbc", "hmac-sha2-256");
+    test_algorithm(*state, "aes128-cbc", "hmac-sha2-512");
 }
 
 static void torture_algorithms_aes192_cbc(void **state) {
-    test_algorithm(*state, "aes192-cbc");
+    test_algorithm(*state, "aes192-cbc", "hmac-sha1");
+    test_algorithm(*state, "aes192-cbc", "hmac-sha2-256");
+    test_algorithm(*state, "aes192-cbc", "hmac-sha2-512");
 }
 
 static void torture_algorithms_aes256_cbc(void **state) {
-    test_algorithm(*state, "aes256-cbc");
+    test_algorithm(*state, "aes256-cbc", "hmac-sha1");
+    test_algorithm(*state, "aes256-cbc", "hmac-sha2-256");
+    test_algorithm(*state, "aes256-cbc", "hmac-sha2-512");
 }
 
 static void torture_algorithms_aes128_ctr(void **state) {
-    test_algorithm(*state, "aes128-ctr");
+    test_algorithm(*state, "aes128-ctr", "hmac-sha1");
+    test_algorithm(*state, "aes128-ctr", "hmac-sha2-256");
+    test_algorithm(*state, "aes128-ctr", "hmac-sha2-512")
 }
 
 static void torture_algorithms_aes192_ctr(void **state) {
-    test_algorithm(*state, "aes192-ctr");
+    test_algorithm(*state, "aes192-ctr", "hmac-sha1");
+    test_algorithm(*state, "aes192-ctr", "hmac-sha2-256");
+    test_algorithm(*state, "aes192-ctr", "hmac-sha2-512");
 }
 
 static void torture_algorithms_aes256_ctr(void **state) {
-    test_algorithm(*state, "aes256-ctr");
+    test_algorithm(*state, "aes256-ctr", "hmac-sha1");
+    test_algorithm(*state, "aes256-ctr", "hmac-sha2-256");
+    test_algorithm(*state, "aes256-ctr", "hmac-sha2-512");
 }
 
 static void torture_algorithms_3des_cbc(void **state) {
-    test_algorithm(*state, "3des-cbc");
+    test_algorithm(*state, "3des-cbc", "hmac-sha1");
+    test_algorithm(*state, "3des-cbc", "hmac-sha2-256");
+    test_algorithm(*state, "3des-cbc", "hmac-sha2-512");
 }
 
 static void torture_algorithms_blowfish_cbc(void **state) {
-    test_algorithm(*state, "blowfish-cbc");
+    test_algorithm(*state, "blowfish-cbc", "hmac-sha1");
+    test_algorithm(*state, "blowfish-cbc", "hmac-sha2-256");
+    test_algorithm(*state, "blowfish-cbc", "hmac-sha2-512");
 }
 
 static void torture_algorithms_zlib(void **state) {
-- 
1.9.1

