* Force conversion to UTF-16 of username and domain if server requires UTF-16.
  * Rewrite conversion function to cleanly convert UTF-8 to UTF-16.
  * Fix bug in length computation in NTLMv2-code.
  * Architecture independent access to NTLM NegotiateFlags.

Signed-off-by: Holger Kummert <holger.kumm...@sophos.com>

---
 src/openvpn/ntlm.c | 169 +++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 144 insertions(+), 25 deletions(-)

diff --git a/src/openvpn/ntlm.c b/src/openvpn/ntlm.c
index 3390bdd..4e4c5da 100644
--- a/src/openvpn/ntlm.c
+++ b/src/openvpn/ntlm.c
@@ -56,6 +56,20 @@
 #endif


+#define UNI_MAX_UTF16               0x0010FFFF
+#define UNI_HALF_BASE               0x00010000
+#define UNI_HALF_MASK               0x000003FF
+#define UNI_HALF_SHIFT              10
+#define UNI_SUR_HIGH_START          0xD800
+#define UNI_SUR_LOW_START           0xDC00
+#define UNI_SUR_LOW_END             0xDFFF
+
+
+#define NTLM_NEGOTIATE_UNICODE      (1<<0)
+#define NTLM_NEGOTIATE_OEM          (1<<1)
+#define NTLM_NEGOTIATE_NTLM2_KEY    (1<<19)
+#define NTLM_NEGOTIATE_TARGET_INFO  (1<<23)
+


 static void
@@ -79,8 +93,10 @@ gen_md4_hash (const char* data, int data_len, char *result)
   const md_kt_t *md4_kt = md_kt_get("MD4");
   char md[MD4_DIGEST_LENGTH];

-  md_full(md4_kt, data, data_len, md);
-  memcpy (result, md, MD4_DIGEST_LENGTH);
+  if (data_len >= 0) {
+    md_full(md4_kt, data, data_len, md);
+    memcpy (result, md, MD4_DIGEST_LENGTH);
+  }
 }

 static void
@@ -140,23 +156,92 @@ unsigned char *my_strupr(unsigned char *str)
 }

 static int
-unicodize (char *dst, const char *src)
+utf8_to_utf16LE (unsigned char *dst, int dst_len, unsigned char *src)
 {
-  /* not really unicode... */
-  int i = 0;
-  do
+  /* Convert UTF-8 to UTF-16 little endian
+   *
+   * http://clang.llvm.org/doxygen/ConvertUTF_8c_source.html
+   */
+  unsigned char *end = src + strlen(src),
+                *dst_start = dst;
+  const char trailingBytesForUTF8[256] = {
+              0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+              0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+              0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+              0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+              0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+              0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+              1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+              2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
+  };
+  const unsigned int offsetsUtf8[6] = { 0x00000000UL, 0x00003080UL, 
0x000E2080UL,
+                                        0x03C82080UL, 0xFA082080UL, 
0x82082080UL };
+
+  while (src < end)
     {
-      dst[i++] = *src;
-      dst[i++] = 0;
+      unsigned int ch = 0,
+                   extraBytes = trailingBytesForUTF8[*src];
+
+      if (extraBytes >= end - src)
+        return -1;
+
+      /* The cases all fall through. */
+      switch (extraBytes) {
+        case 5: ch += *src++; ch <<= 6;
+        case 4: ch += *src++; ch <<= 6;
+        case 3: ch += *src++; ch <<= 6;
+        case 2: ch += *src++; ch <<= 6;
+        case 1: ch += *src++; ch <<= 6;
+        case 0: ch += *src++;
+      }
+      ch -= offsetsUtf8[extraBytes];     /* clean up UTF-8 control bits */
+
+      /* Now 'ch' contains the codepoint of the UTF-8-character. */
+
+      if (ch <= 0xFFFF)
+      {
+        if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) {
+          msg (M_INFO, "Warning: Illegal character value: %x is in reserved 
area of UTF-16 [%x, %x]", UNI_SUR_HIGH_START, UNI_SUR_LOW_END);
+          return -1;
+        }
+        else {    /* normal case */
+          if (dst_len - (dst - dst_start) < 2) {
+            msg (M_INFO, "Warning: Not enough space in destination buffer for 
conversion to UTF-16");
+            return -1;
+          }
+          *dst++ = ch & 0xff;
+          *dst++ = (ch >> 8) & 0xff;
+        }
+      }
+      else if (ch > UNI_MAX_UTF16) {
+          msg (M_INFO, "Warning: Illegal character value :%x is too large for 
UTF-16", ch);
+          return -1;
+      }
+      else {
+        /* character is in range 0xFFFF - 0x10FFFF. */
+        if (dst_len - (dst - dst_start) < 4) {
+          msg (M_INFO, "Warning: Not enough space in destination buffer for 
conversion to UTF-16");
+          return -1;
+        }
+        ch -= UNI_HALF_BASE;
+        *dst++ = ( (ch >> UNI_HALF_SHIFT) + UNI_SUR_HIGH_START)        & 0xff;
+        *dst++ = (((ch >> UNI_HALF_SHIFT) + UNI_SUR_HIGH_START) >> 8) & 0xff;
+        *dst++ = ( (ch & UNI_HALF_MASK) + UNI_SUR_LOW_START)           & 0xff;
+        *dst++ = (((ch & UNI_HALF_MASK) + UNI_SUR_LOW_START) >> 8)    & 0xff;
+      }
     }
-  while (*src++);

-  return i;
+  return dst - dst_start;
 }

 static void
-add_security_buffer(int sb_offset, void *data, int length, unsigned char 
*msg_buf, int *msg_bufpos)
+add_security_buffer(int sb_offset, void *data, int length, unsigned char 
*msg_buf, int *msg_bufpos, int msg_buflen)
 {
+  if ((*msg_bufpos - *msg_buf) + length > msg_buflen) {
+    msg (M_INFO, "Warning: Not enough space in destination buffer");
+    return;
+  }
+
        /* Adds security buffer data to a message and sets security buffer's 
offset and length */
        msg_buf[sb_offset] = (unsigned char)length;
        msg_buf[sb_offset + 2] = msg_buf[sb_offset];
@@ -195,10 +280,12 @@ ntlm_phase_3 (const struct http_proxy_info *p, const char 
*phase_2, struct gc_ar
   char pwbuf[sizeof (p->up.password) * 2]; /* for unicode password */
   char buf2[128]; /* decoded reply from proxy */
   unsigned char phase3[464];
+  unsigned long ntlm_negotiate_flags;

   char md4_hash[MD4_DIGEST_LENGTH+5];
   char challenge[8], ntlm_response[24];
   int i, ret_val;
+  int ntlm_unicode;

        char ntlmv2_response[144];
        char userdomain_u[256]; /* for uppercase unicode username and domain */
@@ -211,7 +298,9 @@ ntlm_phase_3 (const struct http_proxy_info *p, const char 
*phase_2, struct gc_ar
        size_t len;

        char domain[128];
-       char username[128];
+  char domain_u[256];
+  char username[USER_PASS_LEN];
+  char username_u[USER_PASS_LEN*2];
        char *separator;

        bool ntlmv2_enabled = (p->auth_method == HTTP_AUTH_NTLM2);
@@ -238,7 +327,7 @@ ntlm_phase_3 (const struct http_proxy_info *p, const char 
*phase_2, struct gc_ar


   /* fill 1st 16 bytes with md4 hash, disregard terminating null */
-  gen_md4_hash (pwbuf, unicodize (pwbuf, p->up.password) - 2, md4_hash);
+  gen_md4_hash (pwbuf, utf8_to_utf16LE(pwbuf, sizeof (p->up.password) * 2, 
(unsigned char*)p->up.password), md4_hash);

   /* pad to 21 bytes */
   memset(md4_hash + MD4_DIGEST_LENGTH, 0, 5);
@@ -250,23 +339,36 @@ ntlm_phase_3 (const struct http_proxy_info *p, const char 
*phase_2, struct gc_ar
   /* we can be sure that phase_2 is less than 128
    * therefore buf2 needs to be (3/4 * 128) */

+  /* extract NegotiateFlags from bytes 20-23 */
+  ntlm_negotiate_flags = (buf2[0x14] & 0xff) | (buf2[0x15] & 0xff) << 8 | 
(buf2[0x16] & 0xff) << 16 | (buf2[0x17] & 0xff) << 24;
+
+  ntlm_unicode = ntlm_negotiate_flags & NTLM_NEGOTIATE_UNICODE ? 1 : 0;
+
   /* extract the challenge from bytes 24-31 */
   for (i=0; i<8; i++)
   {
     challenge[i] = buf2[i+24];
   }

+  msg (M_DEBUG, "Received NTLM negotiate flags 0x%08x: %s encoding, 
%sNTLM2sec, %sTargetInfo - doing NTLMv%s",
+                 ntlm_negotiate_flags, ntlm_unicode ? "Unicode":"OEM",
+                 ntlm_negotiate_flags & NTLM_NEGOTIATE_NTLM2_KEY ? "":"no ",
+                 ntlm_negotiate_flags & NTLM_NEGOTIATE_TARGET_INFO ? "":"no ",
+                 ntlmv2_enabled ? "2":"1");
+
        if (ntlmv2_enabled){ /* Generate NTLMv2 response */
                int tib_len;

                /* NTLMv2 hash */
-               my_strupr((unsigned char *)strcpy(userdomain, username));
+               my_strupr((unsigned char *)strcpy(userdomain, username));
                if (strlen(username) + strlen(domain) < sizeof(userdomain))
                        strcat(userdomain, domain);
                else
                        msg (M_INFO, "Warning: Username or domain too long");
-               unicodize (userdomain_u, userdomain);
-               gen_hmac_md5(userdomain_u, 2 * strlen(userdomain), md4_hash, 
MD5_DIGEST_LENGTH, ntlmv2_hash);
+               len = utf8_to_utf16LE (userdomain_u, sizeof(userdomain_u), 
userdomain);
+               if (len >= 0) {
+                 gen_hmac_md5(userdomain_u, len, md4_hash, MD5_DIGEST_LENGTH, 
ntlmv2_hash);
+               }

                /* NTLMv2 Blob */
                memset(ntlmv2_blob, 0, 128);                /* Clear blob 
buffer */ 
@@ -278,8 +380,8 @@ ntlm_phase_3 (const struct http_proxy_info *p, const char 
*phase_2, struct gc_ar
                ntlmv2_blob[0x18]=0;                        /* Unknown, zero 
should work */

                /* Add target information block to the blob */
-               if (( *((long *)&buf2[0x14]) & 0x00800000) == 0x00800000){ /* 
Check for Target Information block */
-                       tib_len = buf2[0x28];/* Get Target Information block 
size */
+               if (ntlm_negotiate_flags & NTLM_NEGOTIATE_TARGET_INFO){    /* 
Check for Target Information block */
+                       tib_len = buf2[0x28] & 0xff | (buf2[0x29] & 0xff) << 8; 
 /* Get Target Information block size */
                        if (tib_len > 96) tib_len = 96;
                        {
                          char *tib_ptr = buf2 + buf2[0x2c]; /* Get Target 
Information block pointer */
@@ -292,7 +394,7 @@ ntlm_phase_3 (const struct http_proxy_info *p, const char 
*phase_2, struct gc_ar
                ntlmv2_blob[0x1c + tib_len] = 0;            /* Unknown, zero 
works */ 

                /* Get blob length */
-               ntlmv2_blob_size = 0x20 + tib_len; 
+               ntlmv2_blob_size = 0x1c + tib_len;

                /* Add challenge from message 2 */
                memcpy(&ntlmv2_response[8], challenge, 8);
@@ -323,16 +425,33 @@ ntlm_phase_3 (const struct http_proxy_info *p, const char 
*phase_2, struct gc_ar
        phase3[8] = 3; /* type 3 */

        if (ntlmv2_enabled){ /* NTLMv2 response */
-               add_security_buffer(0x14, ntlmv2_response, ntlmv2_blob_size + 
16, phase3, &phase3_bufpos);
+               add_security_buffer(0x14, ntlmv2_response, ntlmv2_blob_size + 
16, phase3, &phase3_bufpos, sizeof(phase3));
        }else{ /* NTLM response */
-               add_security_buffer(0x14, ntlm_response, 24, phase3, 
&phase3_bufpos);
+               add_security_buffer(0x14, ntlm_response, 24, phase3, 
&phase3_bufpos, sizeof(phase3));
        }

-       /* username in ascii */
-       add_security_buffer(0x24, username, strlen (username), phase3, 
&phase3_bufpos);
+  /* Set username. */
+  if (ntlm_unicode) {
+    len = utf8_to_utf16LE(username_u, sizeof(username_u), username);
+    if (len>=0) {
+      add_security_buffer(0x24, username_u, len, phase3, &phase3_bufpos, 
sizeof(phase3));
+    }
+  }
+  else {
+    add_security_buffer(0x24, username, strlen(username), phase3, 
&phase3_bufpos, sizeof(phase3));
+  }
+

        /* Set domain. If <domain> is empty, default domain will be used (i.e. 
proxy's domain) */ 
-       add_security_buffer(0x1c, domain, strlen (domain), phase3, 
&phase3_bufpos);
+  if (ntlm_unicode) {
+    len = utf8_to_utf16LE(domain_u, sizeof(domain_u), domain);
+    if (len>=0) {
+      add_security_buffer(0x1c, domain_u, len, phase3, &phase3_bufpos, 
sizeof(phase3));
+    }
+  }
+  else {
+    add_security_buffer(0x1c, domain, strlen(domain), phase3, &phase3_bufpos, 
sizeof(phase3));
+  }


        /* other security buffers will be empty */
@@ -341,7 +460,7 @@ ntlm_phase_3 (const struct http_proxy_info *p, const char 
*phase_2, struct gc_ar
        phase3[0x38] = phase3_bufpos; /* no session key */

        /* flags */
-  phase3[0x3c] = 0x02; /* negotiate oem */
+  phase3[0x3c] = ntlm_unicode ? 0x01 : 0x02; /* negotiate unicode/oem */
   phase3[0x3d] = 0x02; /* negotiate ntlm */

   return ((const char *)make_base64_string2 ((unsigned char *)phase3, 
phase3_bufpos, gc));
-- 
tg: (fce39ec..) t/0006/fix_ntlm_http_auth (depends on: t/0005/ignore_utf8_bom)

Reply via email to