From f33ba69e92f389f5884eae5a586858a629a5260b Mon Sep 17 00:00:00 2001 From: simon Date: Mon, 30 Aug 2004 13:11:17 +0000 Subject: [PATCH] Malcolm Smith's patch to support CHAP (digest-based) authentication 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 | 39 +++++++------ cproxy.c | 190 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ nocproxy.c | 36 ++++++++++++ proxy.c | 24 +++++--- proxy.h | 14 +++++ putty.h | 4 +- ssh.h | 7 +++ sshmd5.c | 70 ++++++++++++++++------- 8 files changed, 336 insertions(+), 48 deletions(-) create mode 100644 cproxy.c create mode 100644 nocproxy.c diff --git a/Recipe b/Recipe index 39ffecee..9847d0be 100644 --- 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 index 00000000..7efcc234 --- /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 +#include +#include + +#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 index 00000000..d2aeb978 --- /dev/null +++ b/nocproxy.c @@ -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 +#include +#include + +#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 d3bcca69..190b8c99 100644 --- 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 8f49e60b..35c57982 100644 --- 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 3fb26bcc..b12429f5 100644 --- 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 e43d3c27..d7d17ffc 100644 --- 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]; diff --git a/sshmd5.c b/sshmd5.c index 7fbd8c77..89133408 100644 --- 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 }; -- 2.11.0