Malcolm Smith's patch to support CHAP (digest-based) authentication
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Mon, 30 Aug 2004 13:11:17 +0000 (13:11 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Mon, 30 Aug 2004 13:11:17 +0000 (13:11 +0000)
when talking to SOCKS 5 proxies. Configures itself transparently (if
the proxy offers CHAP it will use it, otherwise it falls back to
ordinary cleartext passwords).

git-svn-id: svn://svn.tartarus.org/sgt/putty@4517 cda61777-01e9-0310-a592-d414129be87e

Recipe
cproxy.c [new file with mode: 0644]
nocproxy.c [new file with mode: 0644]
proxy.c
proxy.h
putty.h
ssh.h
sshmd5.c

diff --git a/Recipe b/Recipe
index 39ffece..9847d0b 100644 (file)
--- a/Recipe
+++ b/Recipe
@@ -207,19 +207,26 @@ CHARSET  = sbcsdat slookup sbcs utf8 toucs fromucs xenc mimeenc macenc localenc
 LIBS     = advapi32.lib user32.lib gdi32.lib comctl32.lib comdlg32.lib
          + shell32.lib winmm.lib imm32.lib winspool.lib
 
+# Network backend sets. This also brings in the relevant attachment
+# to proxy.c depending on whether we're crypto-avoidant or not.
+BE_ALL   = be_all cproxy
+BE_NOSSH = be_nossh nocproxy
+BE_SSH   = be_none cproxy
+BE_NONE  = be_none nocproxy
+
 # ------------------------------------------------------------
 # Definitions of actual programs. The program name, followed by a
 # colon, followed by a list of objects. Also in the list may be the
 # keywords [G] for Windows GUI app, [C] for Console app, [X] for
 # X/GTK Unix app, [U] for command-line Unix app, [M] for Macintosh app.
 
-putty    : [G] GUITERM NONSSH WINSSH be_all WINMISC win_res.res LIBS
-puttytel : [G] GUITERM NONSSH be_nossh WINMISC win_res.res LIBS
-plink    : [C] plink console NONSSH WINSSH be_all logging WINMISC
+putty    : [G] GUITERM NONSSH WINSSH BE_ALL WINMISC win_res.res LIBS
+puttytel : [G] GUITERM NONSSH BE_NOSSH WINMISC win_res.res LIBS
+plink    : [C] plink console NONSSH WINSSH BE_ALL logging WINMISC
          + plink.res LIBS
-pscp     : [C] scp winsftp console WINSSH be_none SFTP wildcard WINMISC
+pscp     : [C] scp winsftp console WINSSH BE_SSH SFTP wildcard WINMISC
          + scp.res LIBS
-psftp    : [C] psftp winsftp console WINSSH be_none SFTP WINMISC scp.res LIBS
+psftp    : [C] psftp winsftp console WINSSH BE_SSH SFTP WINMISC scp.res LIBS
 
 pageant  : [G] pageant sshrsa sshpubk sshdes sshbn sshmd5 version tree234
          + misc sshaes sshsha pageantc sshdss sshsh512 winutils winmisc
@@ -229,28 +236,28 @@ puttygen : [G] puttygen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
          + sshrand noise sshsha winstore misc winctrls sshrsa sshdss winmisc
          + sshpubk sshaes sshsh512 import winutils puttygen.res tree234 LIBS
 
-pterm    : [X] UXTERM uxmisc misc ldisc settings pty uxsel be_none uxstore
+pterm    : [X] UXTERM uxmisc misc ldisc settings pty uxsel BE_NONE uxstore
          + signal CHARSET cmdline ptermm version
-putty    : [X] UXTERM uxmisc misc ldisc settings pty uxsel be_all uxstore
+putty    : [X] UXTERM uxmisc misc ldisc settings pty uxsel BE_ALL uxstore
          + signal CHARSET uxputty NONSSH UXSSH UXMISC ux_x11
-puttytel : [X] UXTERM uxmisc misc ldisc settings pty uxsel be_nossh uxstore
-         + signal CHARSET uxputty NONSSH UXMISC
+puttytel : [X] UXTERM uxmisc misc ldisc settings pty uxsel BE_NOSSH
+        + uxstore signal CHARSET uxputty NONSSH UXMISC
 
-plink    : [U] uxplink uxcons NONSSH UXSSH be_all logging UXMISC signal ux_x11
+plink    : [U] uxplink uxcons NONSSH UXSSH BE_ALL logging UXMISC signal ux_x11
 
 puttygen : [U] cmdgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
          + sshrand uxnoise sshsha misc sshrsa sshdss uxcons uxstore uxmisc
          + sshpubk sshaes sshsh512 import puttygen.res tree234 uxgen
 
-pscp     : [U] scp uxsftp uxcons UXSSH be_none SFTP wildcard UXMISC
-psftp    : [U] psftp uxsftp uxcons UXSSH be_none SFTP UXMISC
+pscp     : [U] scp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC
+psftp    : [U] psftp uxsftp uxcons UXSSH BE_SSH SFTP UXMISC
 
-PuTTY    : [M] terminal wcwidth ldiscucs logging be_all mac macdlg macevlog
+PuTTY    : [M] terminal wcwidth ldiscucs logging BE_ALL mac macdlg macevlog
          + macterm macucs mac_res.rsrc testback NONSSH MACSSH MACMISC CHARSET
          + stricmp vsnprint dialog config macctrls
-PuTTYtel : [M] terminal wcwidth ldiscucs logging be_nossh mac macdlg macevlog
-         + macterm macucs mac_res.rsrc testback NONSSH MACMISC CHARSET
-         + stricmp vsnprint dialog config macctrls
+PuTTYtel : [M] terminal wcwidth ldiscucs logging BE_NOSSH mac macdlg
+        + macevlog macterm macucs mac_res.rsrc testback NONSSH MACMISC
+        + CHARSET stricmp vsnprint dialog config macctrls
 PuTTYgen : [M] macpgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
          + sshrand macnoise sshsha macstore misc sshrsa sshdss macmisc sshpubk
          + sshaes sshsh512 import macpgen.rsrc macpgkey macabout
diff --git a/cproxy.c b/cproxy.c
new file mode 100644 (file)
index 0000000..7efcc23
--- /dev/null
+++ b/cproxy.c
@@ -0,0 +1,190 @@
+/*
+ * Routines to do cryptographic interaction with proxies in PuTTY.
+ * This is in a separate module from proxy.c, so that it can be
+ * conveniently removed in PuTTYtel by replacing this module with
+ * the stub version nocproxy.c.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+
+#define DEFINE_PLUG_METHOD_MACROS
+#include "putty.h"
+#include "ssh.h" /* For MD5 support */
+#include "network.h"
+#include "proxy.h"
+
+static void hmacmd5_chap(const unsigned char *challenge, int challen,
+                        const char *passwd, unsigned char *response)
+{
+    void *hmacmd5_ctx;
+    int pwlen;
+
+    hmacmd5_ctx = hmacmd5_make_context();
+
+    pwlen = strlen(passwd);
+    if (pwlen>64) {
+       unsigned char md5buf[16];
+       MD5Simple(passwd, pwlen, md5buf);
+       hmacmd5_key(hmacmd5_ctx, md5buf, 16);
+    } else {
+       hmacmd5_key(hmacmd5_ctx, passwd, pwlen);
+    }
+
+    hmacmd5_do_hmac(hmacmd5_ctx, challenge, challen, response);
+    hmacmd5_free_context(hmacmd5_ctx);
+}
+
+void proxy_socks5_offerencryptedauth(char *command, int *len)
+{
+    command[*len] = 0x03; /* CHAP */
+    (*len)++;
+}
+
+int proxy_socks5_handlechap (Proxy_Socket p)
+{
+
+    /* CHAP authentication reply format:
+     *  version number (1 bytes) = 1
+     *  number of commands (1 byte)
+     *
+     * For each command:
+     *  command identifier (1 byte)
+     *  data length (1 byte)
+     */
+    unsigned char data[260];
+    unsigned char outbuf[20];
+
+    while(p->chap_num_attributes == 0 ||
+         p->chap_num_attributes_processed < p->chap_num_attributes) {
+       if (p->chap_num_attributes == 0 ||
+           p->chap_current_attribute == -1) {
+           /* CHAP normally reads in two bytes, either at the
+            * beginning or for each attribute/value pair.  But if
+            * we're waiting for the value's data, we might not want
+            * to read 2 bytes.
+            */
+           if (bufchain_size(&p->pending_input_data) < 2)
+               return 1;              /* not got anything yet */
+
+           /* get the response */
+           bufchain_fetch(&p->pending_input_data, data, 2);
+           bufchain_consume(&p->pending_input_data, 2);
+       }
+
+       if (p->chap_num_attributes == 0) {
+           /* If there are no attributes, this is our first msg
+            * with the server, where we negotiate version and 
+            * number of attributes
+            */
+           if (data[0] != 0x01) {
+               plug_closing(p->plug, "Proxy error: SOCKS proxy wants"
+                            " a different CHAP version",
+                            PROXY_ERROR_GENERAL, 0);
+               return 1;
+           }
+           if (data[1] == 0x00) {
+               plug_closing(p->plug, "Proxy error: SOCKS proxy won't"
+                            " negotiate CHAP with us",
+                            PROXY_ERROR_GENERAL, 0);
+               return 1;
+           }
+           p->chap_num_attributes = data[1];
+       } else {
+           if (p->chap_current_attribute == -1) {
+               /* We have to read in each attribute/value pair -
+                * those we don't understand can be ignored, but
+                * there are a few we'll need to handle.
+                */
+               p->chap_current_attribute = data[0];
+               p->chap_current_datalen = data[1];
+           }
+           if (bufchain_size(&p->pending_input_data) <
+               p->chap_current_datalen)
+               return 1;              /* not got everything yet */
+
+           /* get the response */
+           bufchain_fetch(&p->pending_input_data, data,
+                          p->chap_current_datalen);
+
+           bufchain_consume(&p->pending_input_data,
+                            p->chap_current_datalen);
+
+           switch (p->chap_current_attribute) {
+             case 0x00:
+               /* Successful authentication */
+               if (data[0] == 0x00)
+                   p->state = 2;
+               else {
+                   plug_closing(p->plug, "Proxy error: SOCKS proxy"
+                                " refused CHAP authentication",
+                                PROXY_ERROR_GENERAL, 0);
+                   return 1;
+               }
+             break;
+             case 0x03:
+               outbuf[0] = 0x01; /* Version */
+               outbuf[1] = 0x01; /* One attribute */
+               outbuf[2] = 0x04; /* Response */
+               outbuf[3] = 0x10; /* Length */
+               hmacmd5_chap(data, p->chap_current_datalen,
+                            p->cfg.proxy_password, &outbuf[4]);
+               sk_write(p->sub_socket, outbuf, 20);
+             break;
+             case 0x11:
+               /* Chose a protocol */
+               if (data[0] != 0x85) {
+                   plug_closing(p->plug, "Proxy error: Server chose "
+                                "CHAP of other than HMAC-MD5 but we "
+                                "didn't offer it!",
+                                PROXY_ERROR_GENERAL, 0);
+                   return 1;
+               }
+             break;
+           }
+           p->chap_current_attribute = -1;
+           p->chap_num_attributes_processed++;
+       }
+       if (p->state == 8 &&
+           p->chap_num_attributes_processed >= p->chap_num_attributes) {
+           p->chap_num_attributes = 0;
+           p->chap_num_attributes_processed = 0;
+           p->chap_current_datalen = 0;
+       }
+    }
+    return 0;
+}
+
+int proxy_socks5_selectchap(Proxy_Socket p)
+{
+    if (p->cfg.proxy_username[0] || p->cfg.proxy_password[0]) {
+       char chapbuf[514];
+       int ulen;
+       chapbuf[0] = '\x01'; /* Version */
+       chapbuf[1] = '\x02'; /* Number of attributes sent */
+       chapbuf[2] = '\x11'; /* First attribute - algorithms list */
+       chapbuf[3] = '\x01'; /* Only one CHAP algorithm */
+       chapbuf[4] = '\x85'; /* ...and it's HMAC-MD5, the core one */
+       chapbuf[5] = '\x02'; /* Second attribute - username */
+
+       ulen = strlen(p->cfg.proxy_username);
+       if (ulen > 255) ulen = 255; if (ulen < 1) ulen = 1;
+
+       chapbuf[6] = ulen;
+       memcpy(chapbuf+7, p->cfg.proxy_username, ulen);
+
+       sk_write(p->sub_socket, chapbuf, ulen + 7);
+       p->chap_num_attributes = 0;
+       p->chap_num_attributes_processed = 0;
+       p->chap_current_attribute = -1;
+       p->chap_current_datalen = 0;
+
+       p->state = 8;
+    } else 
+       plug_closing(p->plug, "Proxy error: Server chose "
+                    "CHAP authentication but we didn't offer it!",
+                PROXY_ERROR_GENERAL, 0);
+    return 1;
+}
diff --git a/nocproxy.c b/nocproxy.c
new file mode 100644 (file)
index 0000000..d2aeb97
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Routines to refuse to do cryptographic interaction with proxies
+ * in PuTTY. This is a stub implementation of the same interfaces
+ * provided by cproxy.c, for use in PuTTYtel.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+
+#define DEFINE_PLUG_METHOD_MACROS
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+
+void proxy_socks5_offerencryptedauth(char * command, int * len)
+{
+    /* For telnet, don't add any new encrypted authentication routines */
+}
+
+int proxy_socks5_handlechap (Proxy_Socket p)
+{
+
+    plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request"
+                " in telnet-only build",
+                PROXY_ERROR_GENERAL, 0);
+    return 1;
+}
+
+int proxy_socks5_selectchap(Proxy_Socket p)
+{
+    plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request"
+                " in telnet-only build",
+                PROXY_ERROR_GENERAL, 0);
+    return 1;
+}
diff --git a/proxy.c b/proxy.c
index d3bcca6..190b8c9 100644 (file)
--- a/proxy.c
+++ b/proxy.c
@@ -859,15 +859,16 @@ int proxy_socks5_negotiate (Proxy_Socket p, int change)
         *     0x03 = CHAP
         */
 
-       char command[4];
+       char command[5];
        int len;
 
        command[0] = 5; /* version 5 */
        if (p->cfg.proxy_username[0] || p->cfg.proxy_password[0]) {
-           command[1] = 2;            /* two methods supported: */
            command[2] = 0x00;         /* no authentication */
-           command[3] = 0x02;         /* username/password */
-           len = 4;
+           len = 3;
+           proxy_socks5_offerencryptedauth (command, &len);
+           command[len++] = 0x02;             /* username/password */
+           command[1] = len - 2;       /* Number of methods supported */
        } else {
            command[1] = 1;            /* one methods supported: */
            command[2] = 0x00;         /* no authentication */
@@ -923,7 +924,7 @@ int proxy_socks5_negotiate (Proxy_Socket p, int change)
             *    authentication methods:
             *     0x00 = no authentication
             *     0x01 = GSSAPI
-            *     0x02 = username/password 
+            *     0x02 = username/password
             *     0x03 = CHAP
             *     0xff = no acceptable methods
             */
@@ -988,6 +989,12 @@ int proxy_socks5_negotiate (Proxy_Socket p, int change)
            p->state = 2;              /* now proceed as authenticated */
        }
 
+       if (p->state == 8) {
+           int ret;
+           ret = proxy_socks5_handlechap(p);
+           if (ret) return ret;
+       }
+
        if (p->state == 2) {
 
            /* request format:
@@ -1156,10 +1163,9 @@ int proxy_socks5_negotiate (Proxy_Socket p, int change)
        }
 
        if (p->state == 6) {
-           /* TODO: Handle CHAP authentication */
-           plug_closing(p->plug, "Proxy error: We don't support CHAP authentication",
-                        PROXY_ERROR_GENERAL, 0);
-           return 1;
+           int ret;
+           ret = proxy_socks5_selectchap(p);
+           if (ret) return ret;
        }
 
     }
diff --git a/proxy.h b/proxy.h
index 8f49e60..35c5798 100644 (file)
--- a/proxy.h
+++ b/proxy.h
@@ -81,6 +81,12 @@ struct Socket_proxy_tag {
 
     /* configuration, used to look up proxy settings */
     Config cfg;
+
+    /* CHAP transient data */
+    int chap_num_attributes;
+    int chap_num_attributes_processed;
+    int chap_current_attribute;
+    int chap_current_datalen;
 };
 
 typedef struct Plug_proxy_tag * Proxy_Plug;
@@ -106,4 +112,12 @@ extern int proxy_socks5_negotiate (Proxy_Socket, int);
  */
 char *format_telnet_command(SockAddr addr, int port, const Config *cfg);
 
+/*
+ * These are implemented in cproxy.c or nocproxy.c, depending on
+ * whether encrypted proxy authentication is available.
+ */
+extern void proxy_socks5_offerencryptedauth(char *command, int *len);
+extern int proxy_socks5_handlechap (Proxy_Socket p);
+extern int proxy_socks5_selectchap(Proxy_Socket p);
+
 #endif
diff --git a/putty.h b/putty.h
index 3fb26bc..b12429f 100644 (file)
--- a/putty.h
+++ b/putty.h
@@ -338,8 +338,8 @@ struct config_tag {
     int proxy_type;
     char proxy_host[512];
     int proxy_port;
-    char proxy_username[32];
-    char proxy_password[32];
+    char proxy_username[128];
+    char proxy_password[128];
     char proxy_telnet_command[512];
     /* SSH options */
     char remote_cmd[512];
diff --git a/ssh.h b/ssh.h
index e43d3c2..d7d17ff 100644 (file)
--- a/ssh.h
+++ b/ssh.h
@@ -101,6 +101,13 @@ void MD5Init(struct MD5Context *context);
 void MD5Update(struct MD5Context *context, unsigned char const *buf,
               unsigned len);
 void MD5Final(unsigned char digest[16], struct MD5Context *context);
+void MD5Simple(void const *p, unsigned len, unsigned char output[16]);
+
+void *hmacmd5_make_context(void);
+void hmacmd5_free_context(void *handle);
+void hmacmd5_key(void *handle, unsigned char const *key, int len);
+void hmacmd5_do_hmac(void *handle, unsigned char const *blk, int len,
+                    unsigned char *hmac);
 
 typedef struct {
     uint32 h[5];
index 7fbd8c7..8913340 100644 (file)
--- a/sshmd5.c
+++ b/sshmd5.c
@@ -203,22 +203,34 @@ void MD5Final(unsigned char output[16], struct MD5Context *s)
     }
 }
 
+void MD5Simple(void const *p, unsigned len, unsigned char output[16])
+{
+    struct MD5Context s;
+
+    MD5Init(&s);
+    MD5Update(&s, (unsigned char const *)p, len);
+    MD5Final(output, &s);
+}
+
 /* ----------------------------------------------------------------------
  * The above is the MD5 algorithm itself. Now we implement the
  * HMAC wrapper on it.
+ * 
+ * Some of these functions are exported directly, because they are
+ * useful elsewhere (SOCKS5 CHAP authentication uses HMAC-MD5).
  */
 
-static void *md5_make_context(void)
+void *hmacmd5_make_context(void)
 {
     return snewn(2, struct MD5Context);
 }
 
-static void md5_free_context(void *handle)
+void hmacmd5_free_context(void *handle)
 {
     sfree(handle);
 }
 
-static void md5_key_internal(void *handle, unsigned char *key, int len)
+void hmacmd5_key(void *handle, unsigned char const *key, int len)
 {
     struct MD5Context *keys = (struct MD5Context *)handle;
     unsigned char foo[64];
@@ -239,49 +251,65 @@ static void md5_key_internal(void *handle, unsigned char *key, int len)
     memset(foo, 0, 64);                       /* burn the evidence */
 }
 
-static void md5_key(void *handle, unsigned char *key)
+static void hmacmd5_key_16(void *handle, unsigned char *key)
 {
-    md5_key_internal(handle, key, 16);
+    hmacmd5_key(handle, key, 16);
 }
 
-static void md5_do_hmac(void *handle, unsigned char *blk, int len,
-                       unsigned long seq, unsigned char *hmac)
+static void hmacmd5_do_hmac_internal(void *handle,
+                                    unsigned char const *blk, int len,
+                                    unsigned char const *blk2, int len2,
+                                    unsigned char *hmac)
 {
     struct MD5Context *keys = (struct MD5Context *)handle;
     struct MD5Context s;
     unsigned char intermediate[16];
 
-    intermediate[0] = (unsigned char) ((seq >> 24) & 0xFF);
-    intermediate[1] = (unsigned char) ((seq >> 16) & 0xFF);
-    intermediate[2] = (unsigned char) ((seq >> 8) & 0xFF);
-    intermediate[3] = (unsigned char) ((seq) & 0xFF);
-
     s = keys[0];                      /* structure copy */
-    MD5Update(&s, intermediate, 4);
     MD5Update(&s, blk, len);
+    if (blk2) MD5Update(&s, blk2, len2);
     MD5Final(intermediate, &s);
     s = keys[1];                      /* structure copy */
     MD5Update(&s, intermediate, 16);
     MD5Final(hmac, &s);
 }
 
-static void md5_generate(void *handle, unsigned char *blk, int len,
-                        unsigned long seq)
+void hmacmd5_do_hmac(void *handle, unsigned char const *blk, int len,
+                    unsigned char *hmac)
+{
+    hmacmd5_do_hmac_internal(handle, blk, len, NULL, 0, hmac);
+}
+
+static void hmacmd5_do_hmac_ssh(void *handle, unsigned char const *blk, int len,
+                               unsigned long seq, unsigned char *hmac)
+{
+    unsigned char seqbuf[16];
+
+    seqbuf[0] = (unsigned char) ((seq >> 24) & 0xFF);
+    seqbuf[1] = (unsigned char) ((seq >> 16) & 0xFF);
+    seqbuf[2] = (unsigned char) ((seq >> 8) & 0xFF);
+    seqbuf[3] = (unsigned char) ((seq) & 0xFF);
+
+    hmacmd5_do_hmac_internal(handle, seqbuf, 4, blk, len, hmac);
+}
+
+static void hmacmd5_generate(void *handle, unsigned char *blk, int len,
+                            unsigned long seq)
 {
-    md5_do_hmac(handle, blk, len, seq, blk + len);
+    hmacmd5_do_hmac_ssh(handle, blk, len, seq, blk + len);
 }
 
-static int md5_verify(void *handle, unsigned char *blk, int len,
-                     unsigned long seq)
+static int hmacmd5_verify(void *handle, unsigned char *blk, int len,
+                         unsigned long seq)
 {
     unsigned char correct[16];
-    md5_do_hmac(handle, blk, len, seq, correct);
+    hmacmd5_do_hmac_ssh(handle, blk, len, seq, correct);
     return !memcmp(correct, blk + len, 16);
 }
 
 const struct ssh_mac ssh_md5 = {
-    md5_make_context, md5_free_context, md5_key,
-    md5_generate, md5_verify,
+    hmacmd5_make_context, hmacmd5_free_context, hmacmd5_key_16,
+    hmacmd5_generate, hmacmd5_verify,
     "hmac-md5",
     16
 };