From: simon Date: Sun, 12 Jan 2003 14:11:38 +0000 (+0000) Subject: Support for XDM-AUTHORIZATION-1 at the SSH server end, making use of X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/putty/commitdiff_plain/b3ebaa287b8a57f3d35675889adc86f6384eb458 Support for XDM-AUTHORIZATION-1 at the SSH server end, making use of the remote IP/port data provided by the server for forwarded connections. Disabled by default, since it's incompatible with SSH2, probably incompatible with some X clients, and tickles a bug in at least one version of OpenSSH. git-svn-id: svn://svn.tartarus.org/sgt/putty@2554 cda61777-01e9-0310-a592-d414129be87e --- diff --git a/doc/config.but b/doc/config.but index 909d60fb..71cee3e5 100644 --- a/doc/config.but +++ b/doc/config.but @@ -1,4 +1,4 @@ -\versionid $Id: config.but,v 1.47 2002/12/18 16:23:10 simon Exp $ +\versionid $Id: config.but,v 1.48 2003/01/12 14:11:38 simon Exp $ \C{config} Configuring PuTTY @@ -1887,6 +1887,53 @@ display location} box. See \k{using-x-forwarding} for more information about X11 forwarding. +\S2{config-ssh-x11auth} Remote X11 authentication + +\cfg{winhelp-topic}{ssh.tunnels.x11auth} + +If you are using X11 forwarding, the virtual X server created on the +SSH server machine will be protected by authorisation data. This +data is invented, and checked, by PuTTY. + +The usual authorisation method used for this is called +\cw{MIT-MAGIC-COOKIE-1}. This is a simple password-style protocol: +the X client sends some cookie data to the server, and the server +checks that it matches the real cookie. The cookie data is sent over +an unencrypted X11 connection; so if you allow a client on a third +machine to access the virtual X server, then the cookie will be sent +in the clear. + +PuTTY offers the alternative protocol \cw{XDM-AUTHORIZATION-1}. This +is a cryptographically authenticated protocol: the data sent by the +X client is different every time, and it depends on the IP address +and port of the client's end of the connection and is also stamped +with the current time. So an eavesdropper who captures an +\cw{XDM-AUTHORIZATION-1} string cannot immediately re-use it for +their own X connection. + +PuTTY's support for \cw{XDM-AUTHORIZATION-1} is a somewhat +experimental feature, and may encounter several problems: + +\b Some X clients probably do not even support +\cw{XDM-AUTHORIZATION-1}, so they will not know what to do with the +data PuTTY has provided. + +\b This authentication mechanism will only work in SSH v2. In SSH +v1, the SSH server does not tell the client the source address of +a forwarded connection in a machine-readable format, so it's +impossible to verify the \cw{XDM-AUTHORIZATION-1} data. + +\b You may find this feature causes problems with some SSH servers, +which will not clean up \cw{XDM-AUTHORIZATION-1} data after a +session, so that if you then connect to the same server using +a client which only does \cw{MIT-MAGIC-COOKIE-1} and are allocated +the same remote display number, you might find that out-of-date +authentication data is still present on your server and your X +connections fail. + +PuTTY's default is \cw{MIT-MAGIC-COOKIE-1}. If you change it, you +should be sure you know what you're doing. + \S{config-ssh-portfwd} Port forwarding \cfg{winhelp-topic}{ssh.tunnels.portfwd} diff --git a/putty.h b/putty.h index e4d8d1f3..6177bc9d 100644 --- a/putty.h +++ b/putty.h @@ -344,6 +344,7 @@ struct config_tag { /* X11 forwarding */ int x11_forward; char x11_display[128]; + int x11_auth; /* port forwarding */ int lport_acceptall; /* accept conns from hosts other than localhost */ int rport_acceptall; /* same for remote forwarded ports (SSH2 only) */ diff --git a/settings.c b/settings.c index b36affc2..6827eb33 100644 --- a/settings.c +++ b/settings.c @@ -292,6 +292,7 @@ void save_settings(char *section, int do_host, Config * cfg) write_setting_i(sesskey, "BlinkText", cfg->blinktext); write_setting_i(sesskey, "X11Forward", cfg->x11_forward); write_setting_s(sesskey, "X11Display", cfg->x11_display); + write_setting_i(sesskey, "X11AuthType", cfg->x11_auth); write_setting_i(sesskey, "LocalPortAcceptAll", cfg->lport_acceptall); write_setting_i(sesskey, "RemotePortAcceptAll", cfg->rport_acceptall); { @@ -582,6 +583,7 @@ void load_open_settings(void *sesskey, int do_host, Config *cfg) gppi(sesskey, "X11Forward", 0, &cfg->x11_forward); gpps(sesskey, "X11Display", "localhost:0", cfg->x11_display, sizeof(cfg->x11_display)); + gppi(sesskey, "X11AuthType", X11_MIT, &cfg->x11_auth); gppi(sesskey, "LocalPortAcceptAll", 0, &cfg->lport_acceptall); gppi(sesskey, "RemotePortAcceptAll", 0, &cfg->rport_acceptall); diff --git a/ssh.c b/ssh.c index c52301eb..a2e74c0e 100644 --- a/ssh.c +++ b/ssh.c @@ -3018,7 +3018,7 @@ static void ssh1_protocol(Ssh ssh, unsigned char *in, int inlen, int ispkt) char proto[20], data[64]; logevent("Requesting X11 forwarding"); ssh->x11auth = x11_invent_auth(proto, sizeof(proto), - data, sizeof(data)); + data, sizeof(data), cfg.x11_auth); x11_get_real_auth(ssh->x11auth, cfg.x11_display); if (ssh->v1_local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) { send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING, @@ -3277,7 +3277,7 @@ static void ssh1_protocol(Ssh ssh, unsigned char *in, int inlen, int ispkt) c->ssh = ssh; if (x11_init(&c->u.x11.s, cfg.x11_display, c, - ssh->x11auth) != NULL) { + ssh->x11auth, NULL, -1) != NULL) { logevent("opening X11 forward connection failed"); sfree(c); send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, @@ -5068,7 +5068,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt) char proto[20], data[64]; logevent("Requesting X11 forwarding"); ssh->x11auth = x11_invent_auth(proto, sizeof(proto), - data, sizeof(data)); + data, sizeof(data), cfg.x11_auth); x11_get_real_auth(ssh->x11auth, cfg.x11_display); ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_REQUEST); ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid); @@ -5714,6 +5714,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt) } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_OPEN) { char *type; int typelen; + char *peeraddr; + int peeraddrlen; + int port; char *error = NULL; struct ssh_channel *c; unsigned remid, winsize, pktsize; @@ -5724,16 +5727,24 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt) remid = ssh2_pkt_getuint32(ssh); winsize = ssh2_pkt_getuint32(ssh); pktsize = ssh2_pkt_getuint32(ssh); + ssh2_pkt_getstring(ssh, &peeraddr, &peeraddrlen); + port = ssh2_pkt_getuint32(ssh); if (typelen == 3 && !memcmp(type, "x11", 3)) { + char *addrstr = smalloc(peeraddrlen+1); + memcpy(addrstr, peeraddr, peeraddrlen); + peeraddr[peeraddrlen] = '\0'; + if (!ssh->X11_fwd_enabled) error = "X11 forwarding is not enabled"; else if (x11_init(&c->u.x11.s, cfg.x11_display, c, - ssh->x11auth) != NULL) { + ssh->x11auth, addrstr, port) != NULL) { error = "Unable to open an X11 connection"; } else { c->type = CHAN_X11; } + + sfree(addrstr); } else if (typelen == 15 && !memcmp(type, "forwarded-tcpip", 15)) { struct ssh_rportfwd pf, *realpf; diff --git a/ssh.h b/ssh.h index 4a1d03df..640a7f12 100644 --- a/ssh.h +++ b/ssh.h @@ -266,10 +266,10 @@ extern void pfd_unthrottle(Socket s); extern void pfd_override_throttle(Socket s, int enable); /* Exports from x11fwd.c */ -extern char *x11_init(Socket *, char *, void *, void *); +extern char *x11_init(Socket *, char *, void *, void *, const char *, int); extern void x11_close(Socket); extern int x11_send(Socket, char *, int); -extern void *x11_invent_auth(char *, int, char *, int); +extern void *x11_invent_auth(char *, int, char *, int, int); extern void x11_unthrottle(Socket s); extern void x11_override_throttle(Socket s, int enable); extern int x11_get_screen_number(char *display); @@ -368,6 +368,7 @@ void aes256_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len); void des_encrypt_xdmauth(unsigned char *key, unsigned char *blk, int len); +void des_decrypt_xdmauth(unsigned char *key, unsigned char *blk, int len); /* * For progress updates in the key generation utility. diff --git a/sshdes.c b/sshdes.c index ce4bdfe3..4b22c71b 100644 --- a/sshdes.c +++ b/sshdes.c @@ -897,10 +897,9 @@ void des3_encrypt_pubkey_ossh(unsigned char *key, unsigned char *iv, memset(ourkeys, 0, sizeof(ourkeys)); } -void des_encrypt_xdmauth(unsigned char *keydata, unsigned char *blk, int len) +static void des_keysetup_xdmauth(unsigned char *keydata, DESContext *dc) { unsigned char key[8]; - DESContext dc; int i, nbits, j; unsigned int bits; @@ -918,11 +917,23 @@ void des_encrypt_xdmauth(unsigned char *keydata, unsigned char *blk, int len) nbits -= 7; } - des_key_setup(GET_32BIT_MSB_FIRST(key), GET_32BIT_MSB_FIRST(key + 4), - &dc); + des_key_setup(GET_32BIT_MSB_FIRST(key), GET_32BIT_MSB_FIRST(key + 4), dc); +} + +void des_encrypt_xdmauth(unsigned char *keydata, unsigned char *blk, int len) +{ + DESContext dc; + des_keysetup_xdmauth(keydata, &dc); des_cbc_encrypt(blk, blk, 24, &dc); } +void des_decrypt_xdmauth(unsigned char *keydata, unsigned char *blk, int len) +{ + DESContext dc; + des_keysetup_xdmauth(keydata, &dc); + des_cbc_decrypt(blk, blk, 24, &dc); +} + static const struct ssh2_cipher ssh_3des_ssh2 = { des3_make_context, des3_free_context, des3_iv, des3_key, des3_ssh2_encrypt_blk, des3_ssh2_decrypt_blk, diff --git a/windlg.c b/windlg.c index cb19e76d..37d840c1 100644 --- a/windlg.c +++ b/windlg.c @@ -620,6 +620,9 @@ enum { IDCX_ABOUT = IDC_X11_FORWARD, IDC_X11_DISPSTATIC, IDC_X11_DISPLAY, + IDC_X11AUTHSTATIC, + IDC_X11MIT, + IDC_X11XDM, IDC_LPORT_ALL, IDC_RPORT_ALL, IDC_PFWDSTATIC, @@ -1025,6 +1028,10 @@ char *help_context_cmd(int id) case IDC_X11_DISPSTATIC: case IDC_X11_DISPLAY: return "JI(`',`ssh.tunnels.x11')"; + case IDC_X11AUTHSTATIC: + case IDC_X11MIT: + case IDC_X11XDM: + return "JI(`',`ssh.tunnels.x11auth')"; case IDC_PFWDSTATIC: case IDC_PFWDSTATIC2: case IDC_PFWDREMOVE: @@ -1347,6 +1354,8 @@ static void init_dlg_ctrls(HWND hwnd, int keepsess) CheckDlgButton(hwnd, IDC_X11_FORWARD, cfg.x11_forward); SetDlgItemText(hwnd, IDC_X11_DISPLAY, cfg.x11_display); + CheckRadioButton(hwnd, IDC_X11MIT, IDC_X11XDM, + cfg.x11_auth == X11_MIT ? IDC_X11MIT : IDC_X11XDM); CheckDlgButton(hwnd, IDC_LPORT_ALL, cfg.lport_acceptall); CheckDlgButton(hwnd, IDC_RPORT_ALL, cfg.rport_acceptall); @@ -2046,8 +2055,12 @@ static void create_controls(HWND hwnd, int dlgtype, int panel) IDC_TITLE_TUNNELS); beginbox(&cp, "X11 forwarding", IDC_BOX_TUNNELS1); checkbox(&cp, "&Enable X11 forwarding", IDC_X11_FORWARD); - multiedit(&cp, "&X display location", IDC_X11_DISPSTATIC, - IDC_X11_DISPLAY, 50, NULL); + staticedit(&cp, "&X display location", IDC_X11_DISPSTATIC, + IDC_X11_DISPLAY, 50); + radioline(&cp, "Remote X11 a&uthentication protocol", + IDC_X11AUTHSTATIC, 2, + "MIT-Magic-Cookie-1", IDC_X11MIT, + "XDM-Authorization-1", IDC_X11XDM, NULL); endbox(&cp); beginbox(&cp, "Port forwarding", IDC_BOX_TUNNELS2); checkbox(&cp, "Local ports accept connections from o&ther hosts", @@ -3535,6 +3548,16 @@ static int GenericMainDlgProc(HWND hwnd, UINT msg, GetDlgItemText(hwnd, IDC_X11_DISPLAY, cfg.x11_display, sizeof(cfg.x11_display) - 1); break; + case IDC_X11MIT: + case IDC_X11XDM: + if (HIWORD(wParam) == BN_CLICKED || + HIWORD(wParam) == BN_DOUBLECLICKED) { + if (IsDlgButtonChecked(hwnd, IDC_X11MIT)) + cfg.x11_auth = X11_MIT; + else if (IsDlgButtonChecked(hwnd, IDC_X11XDM)) + cfg.x11_auth = X11_XDM; + } + break; case IDC_PFWDADD: if (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED) { diff --git a/x11fwd.c b/x11fwd.c index 52cfd8fe..135c0347 100644 --- a/x11fwd.c +++ b/x11fwd.c @@ -13,18 +13,18 @@ ((unsigned long)(unsigned char)(cp)[3] << 24)) #define PUT_32BIT_LSB_FIRST(cp, value) ( \ - (cp)[0] = (value), \ - (cp)[1] = (value) >> 8, \ - (cp)[2] = (value) >> 16, \ - (cp)[3] = (value) >> 24 ) + (cp)[0] = (char)(value), \ + (cp)[1] = (char)((value) >> 8), \ + (cp)[2] = (char)((value) >> 16), \ + (cp)[3] = (char)((value) >> 24) ) #define GET_16BIT_LSB_FIRST(cp) \ (((unsigned long)(unsigned char)(cp)[0]) | \ ((unsigned long)(unsigned char)(cp)[1] << 8)) #define PUT_16BIT_LSB_FIRST(cp, value) ( \ - (cp)[0] = (value), \ - (cp)[1] = (value) >> 8 ) + (cp)[0] = (char)(value), \ + (cp)[1] = (char)((value) >> 8) ) #define GET_32BIT_MSB_FIRST(cp) \ (((unsigned long)(unsigned char)(cp)[0] << 24) | \ @@ -33,18 +33,18 @@ ((unsigned long)(unsigned char)(cp)[3])) #define PUT_32BIT_MSB_FIRST(cp, value) ( \ - (cp)[0] = (value) >> 24, \ - (cp)[1] = (value) >> 16, \ - (cp)[2] = (value) >> 8, \ - (cp)[3] = (value) ) + (cp)[0] = (char)((value) >> 24), \ + (cp)[1] = (char)((value) >> 16), \ + (cp)[2] = (char)((value) >> 8), \ + (cp)[3] = (char)(value) ) #define GET_16BIT_MSB_FIRST(cp) \ (((unsigned long)(unsigned char)(cp)[0] << 8) | \ ((unsigned long)(unsigned char)(cp)[1])) #define PUT_16BIT_MSB_FIRST(cp, value) ( \ - (cp)[0] = (value) >> 8, \ - (cp)[1] = (value) ) + (cp)[0] = (char)((value) >> 8), \ + (cp)[1] = (char)(value) ) #define GET_16BIT(endian, cp) \ (endian=='B' ? GET_16BIT_MSB_FIRST(cp) : GET_16BIT_LSB_FIRST(cp)) @@ -72,23 +72,35 @@ struct X11Private { int data_read, auth_plen, auth_psize, auth_dlen, auth_dsize; int verified; int throttled, throttle_override; + unsigned long peer_ip; + int peer_port; void *c; /* data used by ssh.c */ Socket s; }; void *x11_invent_auth(char *proto, int protomaxlen, - char *data, int datamaxlen) + char *data, int datamaxlen, int proto_id) { struct X11Auth *auth = smalloc(sizeof(struct X11Auth)); char ourdata[64]; int i; - auth->fakeproto = X11_MIT; + if (proto_id == X11_MIT) { + auth->fakeproto = X11_MIT; - /* MIT-MAGIC-COOKIE-1. Cookie size is 128 bits (16 bytes). */ - auth->fakelen = 16; - for (i = 0; i < 16; i++) - auth->fakedata[i] = random_byte(); + /* MIT-MAGIC-COOKIE-1. Cookie size is 128 bits (16 bytes). */ + auth->fakelen = 16; + for (i = 0; i < 16; i++) + auth->fakedata[i] = random_byte(); + } else { + assert(proto_id == X11_XDM); + auth->fakeproto = X11_XDM; + + /* XDM-AUTHORIZATION-1. Cookie size is 16 bytes; byte 8 is zero. */ + auth->fakelen = 16; + for (i = 0; i < 16; i++) + auth->fakedata[i] = (i == 8 ? 0 : random_byte()); + } /* Now format for the recipient. */ strncpy(proto, x11_authnames[auth->fakeproto], protomaxlen); @@ -116,19 +128,44 @@ void x11_get_real_auth(void *authv, char *display) auth->realdata, &auth->reallen); } -static int x11_verify(struct X11Auth *auth, - char *proto, unsigned char *data, int dlen) +static char *x11_verify(unsigned long peer_ip, int peer_port, + struct X11Auth *auth, char *proto, + unsigned char *data, int dlen) { if (strcmp(proto, x11_authnames[auth->fakeproto]) != 0) - return 0; /* wrong protocol attempted */ + return "wrong authentication protocol attempted"; if (auth->fakeproto == X11_MIT) { if (dlen != auth->fakelen) - return 0; /* cookie was wrong length */ + return "MIT-MAGIC-COOKIE-1 data was wrong length"; if (memcmp(auth->fakedata, data, dlen) != 0) - return 0; /* cookie was wrong cookie! */ + return "MIT-MAGIC-COOKIE-1 data did not match"; + } + if (auth->fakeproto == X11_XDM) { + unsigned long t; + time_t tim; + int i; + + if (dlen != 24) + return "XDM-AUTHORIZATION-1 data was wrong length"; + if (peer_port == -1) + return "cannot do XDM-AUTHORIZATION-1 without remote address data"; + des_decrypt_xdmauth(auth->fakedata+9, data, 24); + if (memcmp(auth->fakedata, data, 8) != 0) + return "XDM-AUTHORIZATION-1 data failed check"; /* cookie wrong */ + if (GET_32BIT_MSB_FIRST(data+8) != peer_ip) + return "XDM-AUTHORIZATION-1 data failed check"; /* IP wrong */ + if ((int)GET_16BIT_MSB_FIRST(data+12) != peer_port) + return "XDM-AUTHORIZATION-1 data failed check"; /* port wrong */ + t = GET_32BIT_MSB_FIRST(data+14); + for (i = 18; i < 24; i++) + if (data[i] != 0) /* zero padding wrong */ + return "XDM-AUTHORIZATION-1 data failed check"; + tim = time(NULL); + if (abs(t - tim) > 20*60) /* 20 minute clock skew should be OK */ + return "XDM-AUTHORIZATION-1 time stamp was too far out"; } /* implement other protocols here if ever required */ - return 1; + return NULL; } static int x11_closing(Plug plug, char *error_msg, int error_code, @@ -189,7 +226,8 @@ int x11_get_screen_number(char *display) * Returns an error message, or NULL on success. * also, fills the SocketsStructure */ -char *x11_init(Socket * s, char *display, void *c, void *auth) +char *x11_init(Socket * s, char *display, void *c, void *auth, + const char *peeraddr, int peerport) { static const struct plug_function_table fn_table = { x11_closing, @@ -253,6 +291,21 @@ char *x11_init(Socket * s, char *display, void *c, void *auth) return err; } + /* + * See if we can make sense of the peer address we were given. + */ + { + int i[4]; + if (peeraddr && + 4 == sscanf(peeraddr, "%d.%d.%d.%d", i+0, i+1, i+2, i+3)) { + pr->peer_ip = (i[0] << 24) | (i[1] << 16) | (i[2] << 8) | i[3]; + pr->peer_port = peerport; + } else { + pr->peer_ip = 0; + pr->peer_port = -1; + } + } + sk_set_private_ptr(*s, pr); sk_addr_free(addr); return NULL; @@ -343,21 +396,26 @@ int x11_send(Socket s, char *data, int len) * If we haven't verified the authentication, do so now. */ if (!pr->verified) { - int ret; + char *err; pr->auth_protocol[pr->auth_plen] = '\0'; /* ASCIZ */ - ret = x11_verify(pr->auth, pr->auth_protocol, + err = x11_verify(pr->peer_ip, pr->peer_port, + pr->auth, pr->auth_protocol, pr->auth_data, pr->auth_dlen); /* * If authentication failed, construct and send an error * packet, then terminate the connection. */ - if (!ret) { - char message[] = "Authentication failed at PuTTY X11 proxy"; - unsigned char reply[8 + sizeof(message) + 4]; - int msglen = sizeof(message) - 1; /* skip zero byte */ - int msgsize = (msglen + 3) & ~3; + if (err) { + char *message; + int msglen, msgsize; + unsigned char *reply; + + message = dupprintf("PuTTY X11 proxy: %s", err); + msglen = strlen(message); + reply = smalloc(8 + msglen+1 + 4); /* include zero byte */ + msgsize = (msglen + 3) & ~3; reply[0] = 0; /* failure */ reply[1] = msglen; /* length of reason string */ memcpy(reply + 2, pr->firstpkt + 2, 4); /* major/minor proto vsn */ @@ -367,6 +425,8 @@ int x11_send(Socket s, char *data, int len) sshfwd_write(pr->c, (char *)reply, 8 + msgsize); sshfwd_close(pr->c); x11_close(s); + sfree(reply); + sfree(message); return 0; }