* 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 | 156 +++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 134 insertions(+), 22 deletions(-)

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


+#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 +84,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,18 +147,89 @@ 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 };
+  const unsigned int uni_max_utf16      = 0x0010FFFF,
+                     uni_half_base      = 0x00010000,
+                     uni_half_mask      = 0x000003FF,
+                     uni_half_shift     = 10,
+                     uni_sur_high_start = 0xD800,
+                     uni_sur_low_start  = 0xDC00,
+                     uni_sur_low_end    = 0xDFFF;
+
+       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: To less 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: To less 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
@@ -195,10 +273,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 +291,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 +320,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 +332,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 +373,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 +387,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);
@@ -328,11 +423,28 @@ ntlm_phase_3 (const struct http_proxy_info *p, const char 
*phase_2, struct gc_ar
                add_security_buffer(0x14, ntlm_response, 24, phase3, 
&phase3_bufpos);
        }

-       /* 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);
+    }
+  }
+  else {
+    add_security_buffer(0x24, username, strlen(username), phase3, 
&phase3_bufpos);
+  }
+

        /* 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);
+    }
+  }
+  else {
+    add_security_buffer(0x1c, domain, strlen(domain), phase3, &phase3_bufpos);
+  }


        /* other security buffers will be empty */
@@ -341,7 +453,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));
-- 
1.8.4.5


Reply via email to