diff --git a/misc.c b/misc.c index 0a86fa61..a28ae8f4 100644 --- a/misc.c +++ b/misc.c @@ -343,6 +343,8 @@ bool nullseat_get_window_pixel_size( Seat *seat, int *width, int *height) { return false; } StripCtrlChars *nullseat_stripctrl_new( Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) {return NULL;} +bool nullseat_set_trust_status(Seat *seat, bool tr) { return false; } +bool nullseat_set_trust_status_vacuously(Seat *seat, bool tr) { return true; } void sk_free_peer_info(SocketPeerInfo *pi) { diff --git a/pscp.c b/pscp.c index 84d0b2b7..29b3e129 100644 --- a/pscp.c +++ b/pscp.c @@ -81,6 +81,7 @@ static const SeatVtable pscp_seat_vt = { nullseat_get_windowid, nullseat_get_window_pixel_size, console_stripctrl_new, + nullseat_set_trust_status_vacuously, }; static Seat pscp_seat[1] = {{ &pscp_seat_vt }}; diff --git a/psftp.c b/psftp.c index 2a1479ad..2483068e 100644 --- a/psftp.c +++ b/psftp.c @@ -62,6 +62,7 @@ static const SeatVtable psftp_seat_vt = { nullseat_get_windowid, nullseat_get_window_pixel_size, console_stripctrl_new, + nullseat_set_trust_status_vacuously, }; static Seat psftp_seat[1] = {{ &psftp_seat_vt }}; diff --git a/putty.h b/putty.h index 6f4edf62..3d30cf08 100644 --- a/putty.h +++ b/putty.h @@ -952,6 +952,20 @@ struct SeatVtable { */ StripCtrlChars *(*stripctrl_new)( Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); + + /* + * Set the seat's current idea of where output is coming from. + * True means that output is being generated by our own code base + * (and hence, can be trusted if it's asking you for secrets such + * as your passphrase); false means output is coming from the + * server. + * + * Returns true if the seat has a way to indicate this + * distinction. Returns false if not, in which case the backend + * should use a fallback defence against spoofing of PuTTY's local + * prompts by malicious servers. + */ + bool (*set_trust_status)(Seat *seat, bool trusted); }; static inline size_t seat_output( @@ -995,6 +1009,8 @@ static inline bool seat_get_window_pixel_size(Seat *seat, int *w, int *h) static inline StripCtrlChars *seat_stripctrl_new( Seat *seat, BinarySink *bs, SeatInteractionContext sic) { return seat->vt->stripctrl_new(seat, bs, sic); } +static inline bool seat_set_trust_status(Seat *seat, bool trusted) +{ return seat->vt->set_trust_status(seat, trusted); } /* Unlike the seat's actual method, the public entry point * seat_connection_fatal is a wrapper function with a printf-like API, @@ -1045,6 +1061,8 @@ bool nullseat_get_windowid(Seat *seat, long *id_out); bool nullseat_get_window_pixel_size(Seat *seat, int *width, int *height); StripCtrlChars *nullseat_stripctrl_new( Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); +bool nullseat_set_trust_status(Seat *seat, bool trusted); +bool nullseat_set_trust_status_vacuously(Seat *seat, bool trusted); /* * Seat functions provided by the platform's console-application @@ -1064,6 +1082,7 @@ int console_confirm_weak_cached_hostkey( void (*callback)(void *ctx, int result), void *ctx); StripCtrlChars *console_stripctrl_new( Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); +bool console_set_trust_status(Seat *seat, bool trusted); /* * Other centralised seat functions. @@ -1627,7 +1646,7 @@ void term_provide_logctx(Terminal *term, LogContext *logctx); void term_set_focus(Terminal *term, bool has_focus); char *term_get_ttymode(Terminal *term, const char *mode); int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input); -void term_set_trust_status(Terminal *term, SeatTrustStatus status); +void term_set_trust_status(Terminal *term, bool trusted); typedef enum SmallKeypadKey { SKK_HOME, SKK_END, SKK_INSERT, SKK_DELETE, SKK_PGUP, SKK_PGDN, diff --git a/raw.c b/raw.c index 1db60e4f..883a074c 100644 --- a/raw.c +++ b/raw.c @@ -130,6 +130,9 @@ static const char *raw_init(Seat *seat, Backend **backend_handle, int addressfamily; char *loghost; + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + raw = snew(Raw); raw->plug.vt = &Raw_plugvt; raw->backend.vt = &raw_backend; diff --git a/sesschan.c b/sesschan.c index 8a9a5d4e..6725a643 100644 --- a/sesschan.c +++ b/sesschan.c @@ -194,6 +194,7 @@ static const SeatVtable sesschan_seat_vt = { nullseat_get_windowid, sesschan_get_window_pixel_size, nullseat_stripctrl_new, + nullseat_set_trust_status, }; Channel *sesschan_new(SshChannel *c, LogContext *logctx, diff --git a/ssh2userauth.c b/ssh2userauth.c index 5c17635b..fc413923 100644 --- a/ssh2userauth.c +++ b/ssh2userauth.c @@ -485,9 +485,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) */ if (bufchain_size(&s->banner) && (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE))) { - if (s->banner_scc) + if (s->banner_scc) { ssh2_userauth_antispoof_msg( s, "Pre-authentication banner message from server:"); + seat_set_trust_status(s->ppl.seat, false); + } bool mid_line = false; while (bufchain_size(&s->banner) > 0) { @@ -502,9 +504,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) if (mid_line) seat_stderr_pl(s->ppl.seat, PTRLEN_LITERAL("\r\n")); - if (s->banner_scc) + if (s->banner_scc) { + seat_set_trust_status(s->ppl.seat, true); ssh2_userauth_antispoof_msg( s, "End of banner message from server"); + } } if (pktin && pktin->type == SSH2_MSG_USERAUTH_SUCCESS) { @@ -1269,6 +1273,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s, "Keyboard-interactive authentication " "prompts from server:"); s->ki_printed_header = true; + seat_set_trust_status(s->ppl.seat, false); } sb = strbuf_new(); @@ -1359,9 +1364,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) /* * Print our trailer line, if we printed a header. */ - if (s->ki_printed_header) + if (s->ki_printed_header) { + seat_set_trust_status(s->ppl.seat, true); ssh2_userauth_antispoof_msg( s, "End of keyboard-interactive prompts from server"); + } /* * We should have SUCCESS or FAILURE now. @@ -1835,9 +1842,23 @@ static void ssh2_userauth_antispoof_msg( struct ssh2_userauth_state *s, const char *msg) { strbuf *sb = strbuf_new(); - strbuf_catf(sb, "-- %s ", msg); - while (sb->len < 78) - put_byte(sb, '-'); + if (seat_set_trust_status(s->ppl.seat, true)) { + /* + * If the seat can directly indicate that this message is + * generated by the client, then we can just use the message + * unmodified as an unspoofable header. + */ + put_datapl(sb, ptrlen_from_asciz(msg)); + } else { + /* + * Otherwise, add enough padding around it that the server + * wouldn't be able to mimic it within our line-length + * constraint. + */ + strbuf_catf(sb, "-- %s ", msg); + while (sb->len < 78) + put_byte(sb, '-'); + } put_datapl(sb, PTRLEN_LITERAL("\r\n")); seat_stderr_pl(s->ppl.seat, ptrlen_from_strbuf(sb)); strbuf_free(sb); diff --git a/sshserver.c b/sshserver.c index a27d38c0..5f6e7dde 100644 --- a/sshserver.c +++ b/sshserver.c @@ -114,6 +114,7 @@ static const SeatVtable server_seat_vt = { nullseat_get_windowid, nullseat_get_window_pixel_size, nullseat_stripctrl_new, + nullseat_set_trust_status, }; static void server_socket_log(Plug *plug, int type, SockAddr *addr, int port, diff --git a/telnet.c b/telnet.c index a773498b..e5996953 100644 --- a/telnet.c +++ b/telnet.c @@ -689,6 +689,9 @@ static const char *telnet_init(Seat *seat, Backend **backend_handle, char *loghost; int addressfamily; + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + telnet = snew(Telnet); telnet->plug.vt = &Telnet_plugvt; telnet->backend.vt = &telnet_backend; diff --git a/terminal.c b/terminal.c index fcb03e50..20866982 100644 --- a/terminal.c +++ b/terminal.c @@ -1810,12 +1810,9 @@ void term_free(Terminal *term) sfree(term); } -void term_set_trust_status(Terminal *term, SeatTrustStatus status) +void term_set_trust_status(Terminal *term, bool trusted) { - /* We don't need to distinguish STS_UNTRUSTED from STS_SESSION, - * because the terminal's method of communicating trust can flip - * back and forth between trusted and untrusted easily. */ - term->trusted = (status == STS_TRUSTED); + term->trusted = trusted; } /* diff --git a/testback.c b/testback.c index 1a71d025..8abcaeb4 100644 --- a/testback.c +++ b/testback.c @@ -76,6 +76,9 @@ static const char *null_init(Seat *seat, Backend **backend_handle, LogContext *logctx, Conf *conf, const char *host, int port, char **realhost, int nodelay, int keepalive) { + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + *backend_handle = NULL; return NULL; } @@ -86,6 +89,9 @@ static const char *loop_init(Seat *seat, Backend **backend_handle, int nodelay, int keepalive) { struct loop_state *st = snew(struct loop_state); + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + st->seat = seat; *backend_handle = &st->backend; return NULL; diff --git a/unix/gtkwin.c b/unix/gtkwin.c index 83aea357..395262bc 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -373,6 +373,7 @@ static const char *gtk_seat_get_x_display(Seat *seat); #ifndef NOT_X_WINDOWS static bool gtk_seat_get_windowid(Seat *seat, long *id); #endif +static bool gtk_seat_set_trust_status(Seat *seat, bool trusted); static const SeatVtable gtk_seat_vt = { gtk_seat_output, @@ -396,6 +397,7 @@ static const SeatVtable gtk_seat_vt = { #endif gtk_seat_get_window_pixel_size, gtk_seat_stripctrl_new, + gtk_seat_set_trust_status, }; static void gtk_eventlog(LogPolicy *lp, const char *string) @@ -5503,3 +5505,10 @@ void new_session_window(Conf *conf, const char *geometry_string) if (inst->ldisc) /* early backend failure might make this NULL already */ ldisc_echoedit_update(inst->ldisc); /* cause ldisc to notice changes */ } + +static bool gtk_seat_set_trust_status(Seat *seat, bool trusted) +{ + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); + term_set_trust_status(inst->term, trusted); + return true; +} diff --git a/unix/uxcons.c b/unix/uxcons.c index fd4999b7..8b966cd4 100644 --- a/unix/uxcons.c +++ b/unix/uxcons.c @@ -414,6 +414,11 @@ static int console_askappend(LogPolicy *lp, Filename *filename, return 0; } +bool console_set_trust_status(Seat *seat, bool trusted) +{ + return false; +} + /* * Warn about the obsolescent key file format. * diff --git a/unix/uxplink.c b/unix/uxplink.c index a654fa23..5de61b22 100644 --- a/unix/uxplink.c +++ b/unix/uxplink.c @@ -401,6 +401,7 @@ static const SeatVtable plink_seat_vt = { nullseat_get_windowid, nullseat_get_window_pixel_size, console_stripctrl_new, + console_set_trust_status, }; static Seat plink_seat[1] = {{ &plink_seat_vt }}; diff --git a/unix/uxpty.c b/unix/uxpty.c index b499fb21..93721358 100644 --- a/unix/uxpty.c +++ b/unix/uxpty.c @@ -868,6 +868,9 @@ Backend *pty_backend_create( Pty *pty; int i; + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + if (single_pty) { pty = single_pty; assert(pty->conf == NULL); diff --git a/unix/uxser.c b/unix/uxser.c index 815d82e7..22391548 100644 --- a/unix/uxser.c +++ b/unix/uxser.c @@ -288,6 +288,9 @@ static const char *serial_init(Seat *seat, Backend **backend_handle, const char *err; char *line; + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + serial = snew(Serial); serial->backend.vt = &serial_backend; *backend_handle = &serial->backend; diff --git a/windows/wincons.c b/windows/wincons.c index bb8bc9a4..a8e382b8 100644 --- a/windows/wincons.c +++ b/windows/wincons.c @@ -279,6 +279,11 @@ int console_confirm_weak_cached_hostkey( } } +bool console_set_trust_status(Seat *seat, bool trusted) +{ + return false; +} + /* * Ask whether to wipe a session log file before writing to it. * Returns 2 for wipe, 1 for append, 0 for cancel (don't log). diff --git a/windows/window.c b/windows/window.c index 5b4242dd..0287cc8b 100644 --- a/windows/window.c +++ b/windows/window.c @@ -339,6 +339,7 @@ static void win_seat_notify_remote_exit(Seat *seat); static void win_seat_connection_fatal(Seat *seat, const char *msg); static void win_seat_update_specials_menu(Seat *seat); static void win_seat_set_busy_status(Seat *seat, BusyStatus status); +static bool win_seat_set_trust_status(Seat *seat, bool trusted); static const SeatVtable win_seat_vt = { win_seat_output, @@ -358,6 +359,7 @@ static const SeatVtable win_seat_vt = { nullseat_get_windowid, win_seat_get_window_pixel_size, win_seat_stripctrl_new, + win_seat_set_trust_status, }; static Seat win_seat_impl = { &win_seat_vt }; Seat *const win_seat = &win_seat_impl; @@ -5824,3 +5826,9 @@ void agent_schedule_callback(void (*callback)(void *, void *, int), c->len = len; PostMessage(hwnd, WM_AGENT_CALLBACK, 0, (LPARAM)c); } + +static bool win_seat_set_trust_status(Seat *seat, bool trusted) +{ + term_set_trust_status(term, trusted); + return true; +} diff --git a/windows/winplink.c b/windows/winplink.c index c3e03afe..f149aea6 100644 --- a/windows/winplink.c +++ b/windows/winplink.c @@ -106,6 +106,7 @@ static const SeatVtable plink_seat_vt = { nullseat_get_windowid, nullseat_get_window_pixel_size, console_stripctrl_new, + console_set_trust_status, }; static Seat plink_seat[1] = {{ &plink_seat_vt }}; diff --git a/windows/winser.c b/windows/winser.c index b11fe581..fd412e38 100644 --- a/windows/winser.c +++ b/windows/winser.c @@ -201,6 +201,9 @@ static const char *serial_init(Seat *seat, Backend **backend_handle, const char *err; char *serline; + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + serial = snew(Serial); serial->port = INVALID_HANDLE_VALUE; serial->out = serial->in = NULL;