From e21c8a539d47a3e9e5367f75ac71ceef55cc10c9 Mon Sep 17 00:00:00 2001
From: Joseph Tetzlaff-Deas <jtetzlaff@barracuda.com>
Date: Wed, 13 Jan 2016 10:21:59 +0000
Subject: [PATCH] SMB: Implemented MAC signing of SMB messages

Added logic in smb.c to sign SMB messages when negotiated with the
server. This is required to talk to windows domain controllers and
other servers that require message signing by default.
---
 lib/smb.c | 313 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 lib/smb.h |   5 +
 2 files changed, 301 insertions(+), 17 deletions(-)

diff --git a/lib/smb.c b/lib/smb.c
index 8e2c164..defe558 100644
--- a/lib/smb.c
+++ b/lib/smb.c
@@ -35,6 +35,30 @@
 #define getpid _getpid
 #endif
 
+#ifdef USE_OPENSSL
+#  ifndef OPENSSL_NO_MD4
+#    include <openssl/md4.h>
+#  endif
+#elif defined(USE_GNUTLS_NETTLE)
+#  include <nettle/md4.h>
+#elif defined(USE_GNUTLS)
+#  include <gcrypt.h>
+#  define MD4_DIGEST_LENGTH 16
+#elif defined(USE_NSS)
+#  include <nss.h>
+#  include <pk11pub.h>
+#  include <hasht.h>
+#  include "curl_md4.h"
+#elif defined(USE_DARWINSSL)
+#  include <CommonCrypto/CommonCryptor.h>
+#  include <CommonCrypto/CommonDigest.h>
+#elif defined(USE_OS400CRYPTO)
+#  include "cipher.mih"  /* mih/cipher */
+#  include "curl_md4.h"
+#elif defined(USE_WIN32_CRYPTO)
+#  include <wincrypt.h>
+#endif
+
 #include "smb.h"
 #include "urldata.h"
 #include "sendf.h"
@@ -46,6 +70,7 @@
 #include "curl_ntlm_core.h"
 #include "escape.h"
 #include "curl_endian.h"
+#include "curl_md5.h"
 
 /* The last #include files should be: */
 #include "curl_memory.h"
@@ -285,6 +310,122 @@ static CURLcode smb_connect(struct connectdata *conn, bool *done)
   return CURLE_OK;
 }
 
+static CURLcode smb_sign(struct connectdata *conn, struct smb_conn *smbc,
+                     size_t msg_len)
+{
+  char *full_msg;
+  unsigned char *security_features;
+  unsigned char MAC[16];
+  unsigned char *MAC_data;
+  MD5_context* MD5c;
+
+  /* The SMB message should be in the upload buffer at this point, as we're
+   * called directly after the header is formatted and copied into the upload
+   * buffer along with the main body of the message.
+   * The structure of the SMB message can be found here:
+   * https://msdn.microsoft.com/en-us/library/ee441702.aspx */
+
+  /* set full_msg to the beginning of the message (start of header) - this
+   * will be +4 bytes into the buffer as the first 4 bytes are not part of
+   * the header */
+  full_msg = conn->data->state.uploadbuffer + 4;
+
+  /* set security_features to the SMB Header field 'SecurityFeatures', which
+   * is 14 bytes into the header (+4 for the non-header bytes) */
+  security_features = (unsigned char*)conn->data->state.uploadbuffer + 18;
+
+  /* put sequence in first 4 bytes of signature field */
+  memset(security_features, 0, 8);
+  security_features[0] = (unsigned char)(smbc->sequence & 0xFF);
+  security_features[1] = (unsigned char)((smbc->sequence >> 8) & 0xFF);
+  security_features[2] = (unsigned char)((smbc->sequence >> 16) & 0xFF);
+  security_features[3] = (unsigned char)((smbc->sequence >> 24) & 0xFF);
+
+  /* calculate MAC signature */
+  /* concat the MAC key and entire SMB message, -4 as the smb_header contains
+   * 4 extra bytes of data which are not a part of the header structure */
+  MAC_data = malloc(sizeof(smbc->MAC_key) + msg_len +
+                    sizeof(struct smb_header) - 4);
+  if(!MAC_data)
+    return CURLE_OUT_OF_MEMORY;
+  memcpy(MAC_data, smbc->MAC_key, sizeof(smbc->MAC_key));
+  memcpy(MAC_data + sizeof(smbc->MAC_key), full_msg, msg_len +
+                                               sizeof(struct smb_header) - 4);
+
+  /* get MD5 hash of all that */
+  MD5c = Curl_MD5_init(Curl_DIGEST_MD5);
+  if(!MD5c) {
+    free(MAC_data);
+    return CURLE_OUT_OF_MEMORY;
+  }
+  Curl_MD5_update(MD5c, MAC_data, (unsigned int)
+                                  (sizeof(smbc->MAC_key) + msg_len +
+                                   sizeof(struct smb_header) - 4));
+  Curl_MD5_final(MD5c, MAC);
+
+  /* put first 8 bytes of MD5 hash in security features */
+  memcpy(security_features, MAC, 8);
+
+  /* increment sequence */
+  smbc->sequence++;
+  free(MAC_data);
+  return CURLE_OK;
+}
+
+static CURLcode smb_check_sign(struct smb_conn *smbc, char *msg,
+                               size_t msg_size)
+{
+  char *fullmsg;
+  char *security_features;
+  unsigned char recv_signature[8];
+  unsigned char MAC[16];
+  unsigned char *MAC_data;
+  MD5_context* MD5c;
+  int i;
+
+  /* check signature here */
+  fullmsg = msg + 4;
+  security_features = msg + 18;
+  memcpy(recv_signature, security_features, 8);
+  MAC_data = malloc(sizeof(smbc->MAC_key) + msg_size);
+  if(!MAC_data)
+    return CURLE_OUT_OF_MEMORY;
+
+  /* get mac key */
+  memcpy(MAC_data, smbc->MAC_key, sizeof(smbc->MAC_key));
+
+  /* put sequence number in sig field */
+  memset(security_features, 0, 8);
+  security_features[0] = (unsigned char)(smbc->sequence & 0xFF);
+  security_features[1] = (unsigned char)((smbc->sequence >> 8) & 0xFF);
+  security_features[2] = (unsigned char)((smbc->sequence >> 16) & 0xFF);
+  security_features[3] = (unsigned char)((smbc->sequence >> 24) & 0xFF);
+
+  /* concat mac key with received SMB message */
+  memcpy(MAC_data + sizeof(smbc->MAC_key), fullmsg, msg_size);
+
+  /* MD5 the whole lot */
+  MD5c = Curl_MD5_init(Curl_DIGEST_MD5);
+  if(!MD5c) {
+    free(MAC_data);
+    return CURLE_OUT_OF_MEMORY;
+  }
+  Curl_MD5_update(MD5c, MAC_data, (unsigned int)
+                                  (sizeof(smbc->MAC_key) + msg_size));
+  Curl_MD5_final(MD5c, MAC);
+
+  /* check first 8 bytes against 8 bytes sig from SMB */
+  free(MAC_data);
+  for(i = 0; i < 8; i++) {
+    if(recv_signature[i] != MAC[i])
+      return CURLE_RECV_ERROR;
+  }
+
+  /* increment sequence */
+  smbc->sequence++;
+  return CURLE_OK;
+}
+
 static CURLcode smb_recv_message(struct connectdata *conn, void **msg)
 {
   struct smb_conn *smbc = &conn->proto.smbc;
@@ -328,6 +469,9 @@ static CURLcode smb_recv_message(struct connectdata *conn, void **msg)
 
   *msg = buf;
 
+  /* Check message signing */
+  if(smbc->sig_required)
+    return smb_check_sign(smbc, *msg, msg_size - 4);
   return CURLE_OK;
 }
 
@@ -408,11 +552,21 @@ static CURLcode smb_flush(struct connectdata *conn)
 static CURLcode smb_send_message(struct connectdata *conn, unsigned char cmd,
                                  const void *msg, size_t msg_len)
 {
+  struct smb_conn *smbc = &conn->proto.smbc;
+  CURLcode result;
+
   smb_format_message(conn, (struct smb_header *)conn->data->state.uploadbuffer,
                      cmd, msg_len);
   memcpy(conn->data->state.uploadbuffer + sizeof(struct smb_header),
          msg, msg_len);
 
+  /* Message signing */
+  if(smbc->sig_required) {
+    result = smb_sign(conn, smbc, msg_len);
+    if(result)
+      return result;
+  }
+
   return smb_send(conn, sizeof(struct smb_header) + msg_len, 0);
 }
 
@@ -428,9 +582,7 @@ static CURLcode smb_send_setup(struct connectdata *conn)
   struct smb_conn *smbc = &conn->proto.smbc;
   struct smb_setup msg;
   char *p = msg.bytes;
-  unsigned char lm_hash[21];
   unsigned char lm[24];
-  unsigned char nt_hash[21];
   unsigned char nt[24];
 
   size_t byte_count = sizeof(lm) + sizeof(nt);
@@ -439,14 +591,9 @@ static CURLcode smb_send_setup(struct connectdata *conn)
   if(byte_count > sizeof(msg.bytes))
     return CURLE_FILESIZE_EXCEEDED;
 
-  Curl_ntlm_core_mk_lm_hash(conn->data, conn->passwd, lm_hash);
-  Curl_ntlm_core_lm_resp(lm_hash, smbc->challenge, lm);
-#if USE_NTRESPONSES
-  Curl_ntlm_core_mk_nt_hash(conn->data, conn->passwd, nt_hash);
-  Curl_ntlm_core_lm_resp(nt_hash, smbc->challenge, nt);
-#else
-  memset(nt, 0, sizeof(nt));
-#endif
+  /* get the responses */
+  memcpy(lm, smbc->lm_resp, 24);
+  memcpy(nt, smbc->nt_resp, 24);
 
   memset(&msg, 0, sizeof(msg));
   msg.word_count = SMB_WC_SETUP_ANDX;
@@ -573,26 +720,58 @@ static CURLcode smb_send_write(struct connectdata *conn)
 {
   struct smb_write *msg = (struct smb_write *)conn->data->state.uploadbuffer;
   struct smb_request *req = conn->data->req.protop;
+  struct smb_conn *smbc = &conn->proto.smbc;
+  CURLcode result;
   curl_off_t offset = conn->data->req.offset;
-
   curl_off_t upload_size = conn->data->req.size - conn->data->req.bytecount;
+  int nread;
+  size_t msgsize, fullmsgsize;
+
   if(upload_size >= MAX_PAYLOAD_SIZE - 1) /* There is one byte of padding */
     upload_size = MAX_PAYLOAD_SIZE - 1;
 
-  memset(msg, 0, sizeof(*msg));
+  msgsize = sizeof(*msg);
+
+  /* Ensure we will read no more than BUFSIZE, taking into account the message
+   * that we will be appending to */
+  nread = upload_size > (int)(BUFSIZE - msgsize) ? (int)(BUFSIZE - msgsize) :
+                                              (int)upload_size;
+
+  memset(msg, 0, msgsize);
   msg->word_count = SMB_WC_WRITE_ANDX;
   msg->andx.command = SMB_COM_NO_ANDX_COMMAND;
   msg->fid = smb_swap16(req->fid);
   msg->offset = smb_swap32((unsigned int) offset);
   msg->offset_high = smb_swap32((unsigned int) (offset >> 32));
-  msg->data_length = smb_swap16((unsigned short) upload_size);
-  msg->data_offset = smb_swap16(sizeof(*msg) - sizeof(unsigned int));
-  msg->byte_count = smb_swap16((unsigned short) (upload_size + 1));
+  msg->data_offset = smb_swap16((unsigned short)
+                                        (msgsize - sizeof(unsigned int)));
 
   smb_format_message(conn, &msg->h, SMB_COM_WRITE_ANDX,
-                     sizeof(*msg) - sizeof(msg->h) + (size_t) upload_size);
+                     msgsize - sizeof(msg->h) + (size_t)nread);
+
+  /* Read the file and append it here - this is necessary in order to have
+   * the full message for signing correctly */
+  fullmsgsize = msgsize + nread;
+  conn->data->req.upload_fromhere = conn->data->state.uploadbuffer + msgsize;
+  result = Curl_fillreadbuffer(conn, nread, &nread);
+  if(result && result != CURLE_AGAIN)
+    return result;
 
-  return smb_send(conn, sizeof(*msg), (size_t) upload_size);
+  if(!nread)
+    return CURLE_OK;
+
+  msg->data_length = smb_swap16((unsigned short) nread);
+  msg->byte_count = smb_swap16((unsigned short) nread);
+
+  /* sign the full message */
+  if(smbc->sig_required) {
+    result = smb_sign(conn, &conn->proto.smbc, fullmsgsize
+                                               - sizeof(struct smb_header));
+    if(result)
+      return result;
+  }
+
+  return smb_send(conn, fullmsgsize, 0);
 }
 
 static CURLcode smb_send_and_recv(struct connectdata *conn, void **msg)
@@ -630,6 +809,97 @@ static CURLcode smb_send_and_recv(struct connectdata *conn, void **msg)
   return smb_recv_message(conn, msg);
 }
 
+static void smb_passwd_hash(struct connectdata *conn, struct smb_conn *smbc) {
+  unsigned char lm_hash[21];
+  unsigned char lm[24];
+  unsigned char nt_hash[21];
+  unsigned char nt[24];
+  unsigned char ntlm_hash[16];
+  unsigned char session_key[16];
+  unsigned char MAC_key[40];
+#ifdef USE_OPENSSL
+  MD4_CTX MD4pw;
+#elif defined(USE_GNUTLS_NETTLE)
+  struct md4_ctx MD4pw;
+#elif defined(USE_GNUTLS)
+  gcry_md_hd_t MD4pw;
+#elif defined(USE_WIN32_CRYPTO)
+  HCRYPTPROV hprov;
+  HCRYPTHASH hhash;
+  DWORD length = 16;
+#endif
+
+  /* create the hashes & responses */
+  Curl_ntlm_core_mk_lm_hash(conn->data, conn->passwd, lm_hash);
+  Curl_ntlm_core_lm_resp(lm_hash, smbc->challenge, lm);
+  memcpy(smbc->lm_resp, lm, sizeof(lm));
+#if USE_NTRESPONSES
+  Curl_ntlm_core_mk_nt_hash(conn->data, conn->passwd, nt_hash);
+  Curl_ntlm_core_lm_resp(nt_hash, smbc->challenge, nt);
+  memcpy(smbc->nt_resp, nt, sizeof(nt));
+  if(smbc->sig_required) {
+
+    /* generate the NTLM session key for signing */
+    memset(session_key, 0, 16);
+    memcpy(ntlm_hash, nt_hash, sizeof(ntlm_hash));
+
+#ifdef USE_OPENSSL
+    MD4_Init(&MD4pw);
+    MD4_Update(&MD4pw, ntlm_hash, sizeof(ntlm_hash));
+    MD4_Final(session_key, &MD4pw);
+#elif defined(USE_GNUTLS_NETTLE)
+    md4_init(&MD4pw);
+    md4_update(&MD4pw, (unsigned int)(sizeof(ntlm_hash)), ntlm_hash);
+    md4_digest(&MD4pw, MD4_DIGEST_SIZE, session_key);
+#elif defined(USE_GNUTLS)
+    gcry_md_open(&MD4pw, GCRY_MD_MD4, 0);
+    gcry_md_write(MD4pw, ntlm_hash, sizeof(ntlm_hash));
+    memcpy (session_key, gcry_md_read (MD4pw, 0), MD4_DIGEST_LENGTH);
+    gcry_md_close(MD4pw);
+#elif defined(USE_NSS) || defined(USE_OS400CRYPTO)
+    Curl_md4it(session_key, ntlm_hash, sizeof(ntlm_hash));
+#elif defined(USE_DARWINSSL)
+    (void)CC_MD4(ntlm_hash, (CC_LONG)(sizeof(ntlm_hash)), session_key);
+#elif defined(USE_WIN32_CRYPTO)
+    if(CryptAcquireContext(&hprov, NULL, NULL, PROV_RSA_FULL,
+                           CRYPT_VERIFYCONTEXT)) {
+      if(CryptCreateHash(hprov, CALG_MD4, 0, 0, &hhash)) {
+        CryptHashData(hhash, ntlm_hash, (unsigned int)sizeof(ntlm_hash), 0);
+        CryptGetHashParam(hhash, HP_HASHVAL, session_key, &length, 0);
+        CryptDestroyHash(hhash);
+      }
+      CryptReleaseContext(hprov, 0);
+    }
+#endif
+
+    /* create MAC key */
+    memcpy(MAC_key, session_key, 16);
+    memcpy(MAC_key + 16, smbc->nt_resp, 24);
+    memcpy(smbc->MAC_key, MAC_key, sizeof(smbc->MAC_key));
+
+    /* init sequence to 0 */
+    smbc->sequence = 0;
+  }
+#else
+  memset(smbc->nt_resp, 0, sizeof(smbc->nt_resp));
+    if(smbc->sec_sig_req) {
+
+      /* generate the LM session key for signing */
+      memset(session_key, 0, 16);
+      memcpy(session_key, lm_hash, 8);
+      memcpy(session_key + 8, "\0\0\0\0\0\0\0\0", 8);
+
+      /* create MAC key */
+      memcpy(MAC_key, session_key, 16);
+      memcpy(MAC_key + 16, smbc->lm_resp, 24);
+      memcpy(smbc->MAC_key, MAC_key, sizeof(smbc->MAC_key));
+
+      /* init sequence to 0 */
+      smbc->sequence = 0;
+    }
+#endif
+}
+
 static CURLcode smb_connection_state(struct connectdata *conn, bool *done)
 {
   struct smb_conn *smbc = &conn->proto.smbc;
@@ -680,6 +950,15 @@ static CURLcode smb_connection_state(struct connectdata *conn, bool *done)
     nrsp = msg;
     memcpy(smbc->challenge, nrsp->bytes, sizeof(smbc->challenge));
     smbc->session_key = smb_swap32(nrsp->session_key);
+
+    /* Check security mode for message signing required */
+    if(nrsp->security_mode & 0x08)
+      smbc->sig_required = true;
+    else
+      smbc->sig_required = false;
+
+    smb_passwd_hash(conn, smbc);
+
     result = smb_send_setup(conn);
     if(result) {
       connclose(conn, "SMB: failed to send setup message");
diff --git a/lib/smb.h b/lib/smb.h
index 7852fa1..13b6982 100644
--- a/lib/smb.h
+++ b/lib/smb.h
@@ -35,6 +35,9 @@ struct smb_conn {
   char *user;
   char *domain;
   unsigned char challenge[8];
+  unsigned char lm_resp[24];
+  unsigned char nt_resp[24];
+  unsigned char MAC_key[40];
   unsigned int session_key;
   unsigned short uid;
   char *recv_buf;
@@ -42,6 +45,8 @@ struct smb_conn {
   size_t send_size;
   size_t sent;
   size_t got;
+  unsigned int sequence;
+  bool sig_required;
 };
 
 /*
-- 
1.9.1

