mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-03-28 09:17:07 -05:00
Merge tag '0.80'.
This involved a trivial merge conflict fix in terminal.c because of the way the cherry-pick 73b41feba5af998 differed from its original bdbd5f429c79a67. But a more significant rework was needed in windows/console.c, because the updates to confirm_weak_* conflicted with the changes on main to abstract out the ConsoleIO system.
This commit is contained in:
commit
968ac6dbf0
2
Buildscr
2
Buildscr
@ -35,7 +35,7 @@ module putty
|
||||
ifeq "$(RELEASE)" "" set Ndate $(!builddate)
|
||||
ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -pe 's/(....)(..)(..)/$$1-$$2-$$3/' > date
|
||||
ifneq "$(Ndate)" "" read Date date
|
||||
set Epoch 18595 # update this at every release
|
||||
set Epoch 18707 # update this at every release
|
||||
ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -ne 'use Time::Local; /(....)(..)(..)/ and print timegm(0,0,0,$$3,$$2-1,$$1) / 86400 - $(Epoch)' > days
|
||||
ifneq "$(Ndate)" "" read Days days
|
||||
|
||||
|
@ -1 +1 @@
|
||||
0.79
|
||||
0.80
|
||||
|
14
console.c
14
console.c
@ -9,18 +9,6 @@
|
||||
#include "misc.h"
|
||||
#include "console.h"
|
||||
|
||||
const char weakcrypto_msg_common_fmt[] =
|
||||
"The first %s supported by the server is\n"
|
||||
"%s, which is below the configured warning threshold.\n";
|
||||
|
||||
const char weakhk_msg_common_fmt[] =
|
||||
"The first host key type we have stored for this server\n"
|
||||
"is %s, which is below the configured warning threshold.\n"
|
||||
"The server also provides the following types of host key\n"
|
||||
"above the threshold, which we do not have stored:\n"
|
||||
"%s\n";
|
||||
|
||||
const char console_continue_prompt[] = "Continue with connection? (y/n) ";
|
||||
const char console_abandoned_msg[] = "Connection abandoned.\n";
|
||||
|
||||
const SeatDialogPromptDescriptions *console_prompt_descriptions(Seat *seat)
|
||||
@ -30,6 +18,8 @@ const SeatDialogPromptDescriptions *console_prompt_descriptions(Seat *seat)
|
||||
.hk_connect_once_action = "enter \"n\"",
|
||||
.hk_cancel_action = "press Return",
|
||||
.hk_cancel_action_Participle = "Pressing Return",
|
||||
.weak_accept_action = "enter \"y\"",
|
||||
.weak_cancel_action = "enter \"n\"",
|
||||
};
|
||||
return &descs;
|
||||
}
|
||||
|
@ -59,23 +59,26 @@ static ssh_cipher *aes_select(const ssh_cipheralg *alg)
|
||||
__VA_ARGS__ \
|
||||
}
|
||||
|
||||
AES_SELECTOR_VTABLE(cbc, "aes128-cbc", "CBC", 128, );
|
||||
AES_SELECTOR_VTABLE(cbc, "aes192-cbc", "CBC", 192, );
|
||||
AES_SELECTOR_VTABLE(cbc, "aes256-cbc", "CBC", 256, );
|
||||
AES_SELECTOR_VTABLE(cbc, "aes128-cbc", "CBC", 128, .flags = SSH_CIPHER_IS_CBC);
|
||||
AES_SELECTOR_VTABLE(cbc, "aes192-cbc", "CBC", 192, .flags = SSH_CIPHER_IS_CBC);
|
||||
AES_SELECTOR_VTABLE(cbc, "aes256-cbc", "CBC", 256, .flags = SSH_CIPHER_IS_CBC);
|
||||
AES_SELECTOR_VTABLE(sdctr, "aes128-ctr", "SDCTR", 128, );
|
||||
AES_SELECTOR_VTABLE(sdctr, "aes192-ctr", "SDCTR", 192, );
|
||||
AES_SELECTOR_VTABLE(sdctr, "aes256-ctr", "SDCTR", 256, );
|
||||
AES_SELECTOR_VTABLE(gcm, "aes128-gcm@openssh.com", "GCM", 128,
|
||||
.required_mac = &ssh2_aesgcm_mac);
|
||||
.required_mac = &ssh2_aesgcm_mac,
|
||||
.flags = SSH_CIPHER_SEPARATE_LENGTH);
|
||||
AES_SELECTOR_VTABLE(gcm, "aes256-gcm@openssh.com", "GCM", 256,
|
||||
.required_mac = &ssh2_aesgcm_mac);
|
||||
.required_mac = &ssh2_aesgcm_mac,
|
||||
.flags = SSH_CIPHER_SEPARATE_LENGTH);
|
||||
|
||||
/* 192-bit AES-GCM is included only so that testcrypt can run standard
|
||||
* test vectors against it. OpenSSH doesn't define a protocol id for
|
||||
* it. Hence setting its ssh2_id to NULL here, and more importantly,
|
||||
* leaving it out of aesgcm_list[] below. */
|
||||
AES_SELECTOR_VTABLE(gcm, NULL, "GCM", 192,
|
||||
.required_mac = &ssh2_aesgcm_mac);
|
||||
.required_mac = &ssh2_aesgcm_mac,
|
||||
.flags = SSH_CIPHER_SEPARATE_LENGTH);
|
||||
|
||||
static const ssh_cipheralg ssh_rijndael_lysator = {
|
||||
/* Same as aes256_cbc, but with a different protocol ID */
|
||||
@ -84,6 +87,7 @@ static const ssh_cipheralg ssh_rijndael_lysator = {
|
||||
.blksize = 16,
|
||||
.real_keybits = 256,
|
||||
.padded_keybytes = 256/8,
|
||||
.flags = SSH_CIPHER_IS_CBC,
|
||||
.text_name = "AES-256 CBC (dummy selector vtable)",
|
||||
.extra = ssh_aes256_cbc_impls,
|
||||
};
|
||||
|
@ -41,7 +41,7 @@ use Plink:
|
||||
|
||||
\c C:\>plink
|
||||
\c Plink: command-line connection utility
|
||||
\c Release 0.79
|
||||
\c Release 0.80
|
||||
\c Usage: plink [options] [user@]host [command]
|
||||
\c ("host" can also be a PuTTY saved session name)
|
||||
\c Options:
|
||||
|
@ -39,7 +39,7 @@ use PSCP:
|
||||
|
||||
\c C:\>pscp
|
||||
\c PuTTY Secure Copy client
|
||||
\c Release 0.79
|
||||
\c Release 0.80
|
||||
\c Usage: pscp [options] [user@]host:source target
|
||||
\c pscp [options] source [source...] [user@]host:target
|
||||
\c pscp [options] -ls [user@]host:filespec
|
||||
|
@ -438,8 +438,32 @@ static SeatPromptResult sshproxy_confirm_ssh_host_key(
|
||||
return SPR_SW_ABORT("Noninteractive SSH proxy cannot confirm host key");
|
||||
}
|
||||
|
||||
static void sshproxy_format_seatdialogtext(strbuf *sb, SeatDialogText *text)
|
||||
{
|
||||
for (SeatDialogTextItem *item = text->items,
|
||||
*end = item+text->nitems; item < end; item++) {
|
||||
switch (item->type) {
|
||||
case SDT_SCARY_HEADING:
|
||||
case SDT_PARA:
|
||||
case SDT_DISPLAY:
|
||||
put_stringz(sb, item->text);
|
||||
put_byte(sb, '\n');
|
||||
break;
|
||||
case SDT_BATCH_ABORT:
|
||||
put_stringz(sb, item->text);
|
||||
put_byte(sb, '\n');
|
||||
goto endloop;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
endloop:
|
||||
while (strbuf_chomp(sb, '\n'));
|
||||
}
|
||||
|
||||
static SeatPromptResult sshproxy_confirm_weak_crypto_primitive(
|
||||
Seat *seat, const char *algtype, const char *algname,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{
|
||||
SshProxy *sp = container_of(seat, SshProxy, seat);
|
||||
@ -450,22 +474,24 @@ static SeatPromptResult sshproxy_confirm_weak_crypto_primitive(
|
||||
* request on to it.
|
||||
*/
|
||||
return seat_confirm_weak_crypto_primitive(
|
||||
wrap(sp->clientseat), algtype, algname, callback, ctx);
|
||||
wrap(sp->clientseat), text, callback, ctx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Otherwise, behave as if we're in batch mode: take the safest
|
||||
* option.
|
||||
*/
|
||||
sshproxy_error(sp, "First %s supported by server is %s, below warning "
|
||||
"threshold. Abandoning proxy SSH connection.",
|
||||
algtype, algname);
|
||||
strbuf *sb = strbuf_new();
|
||||
sshproxy_format_seatdialogtext(sb, text);
|
||||
sshproxy_error(sp, sb->s);
|
||||
strbuf_free(sb);
|
||||
|
||||
return SPR_SW_ABORT("Noninteractive SSH proxy cannot confirm "
|
||||
"weak crypto primitive");
|
||||
}
|
||||
|
||||
static SeatPromptResult sshproxy_confirm_weak_cached_hostkey(
|
||||
Seat *seat, const char *algname, const char *betteralgs,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{
|
||||
SshProxy *sp = container_of(seat, SshProxy, seat);
|
||||
@ -476,16 +502,18 @@ static SeatPromptResult sshproxy_confirm_weak_cached_hostkey(
|
||||
* request on to it.
|
||||
*/
|
||||
return seat_confirm_weak_cached_hostkey(
|
||||
wrap(sp->clientseat), algname, betteralgs, callback, ctx);
|
||||
wrap(sp->clientseat), text, callback, ctx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Otherwise, behave as if we're in batch mode: take the safest
|
||||
* option.
|
||||
*/
|
||||
sshproxy_error(sp, "First host key type stored for server is %s, below "
|
||||
"warning threshold. Abandoning proxy SSH connection.",
|
||||
algname);
|
||||
strbuf *sb = strbuf_new();
|
||||
sshproxy_format_seatdialogtext(sb, text);
|
||||
sshproxy_error(sp, sb->s);
|
||||
strbuf_free(sb);
|
||||
|
||||
return SPR_SW_ABORT("Noninteractive SSH proxy cannot confirm "
|
||||
"weak cached host key");
|
||||
}
|
||||
|
24
putty.h
24
putty.h
@ -1284,7 +1284,7 @@ struct SeatVtable {
|
||||
* confirm_ssh_host_key above.
|
||||
*/
|
||||
SeatPromptResult (*confirm_weak_crypto_primitive)(
|
||||
Seat *seat, const char *algtype, const char *algname,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
|
||||
|
||||
/*
|
||||
@ -1295,11 +1295,10 @@ struct SeatVtable {
|
||||
* This form is used in the case where we're using a host key
|
||||
* below the warning threshold because that's the best one we have
|
||||
* cached, but at least one host key algorithm *above* the
|
||||
* threshold is available that we don't have cached. 'betteralgs'
|
||||
* lists the better algorithm(s).
|
||||
* threshold is available that we don't have cached.
|
||||
*/
|
||||
SeatPromptResult (*confirm_weak_cached_hostkey)(
|
||||
Seat *seat, const char *algname, const char *betteralgs,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
|
||||
|
||||
/*
|
||||
@ -1435,15 +1434,15 @@ static inline SeatPromptResult seat_confirm_ssh_host_key(
|
||||
{ return iseat.seat->vt->confirm_ssh_host_key(
|
||||
iseat.seat, h, p, ktyp, kstr, text, helpctx, cb, ctx); }
|
||||
static inline SeatPromptResult seat_confirm_weak_crypto_primitive(
|
||||
InteractionReadySeat iseat, const char *atyp, const char *aname,
|
||||
InteractionReadySeat iseat, SeatDialogText *text,
|
||||
void (*cb)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{ return iseat.seat->vt->confirm_weak_crypto_primitive(
|
||||
iseat.seat, atyp, aname, cb, ctx); }
|
||||
iseat.seat, text, cb, ctx); }
|
||||
static inline SeatPromptResult seat_confirm_weak_cached_hostkey(
|
||||
InteractionReadySeat iseat, const char *aname, const char *better,
|
||||
InteractionReadySeat iseat, SeatDialogText *text,
|
||||
void (*cb)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{ return iseat.seat->vt->confirm_weak_cached_hostkey(
|
||||
iseat.seat, aname, better, cb, ctx); }
|
||||
iseat.seat, text, cb, ctx); }
|
||||
static inline const SeatDialogPromptDescriptions *seat_prompt_descriptions(
|
||||
Seat *seat)
|
||||
{ return seat->vt->prompt_descriptions(seat); }
|
||||
@ -1497,6 +1496,7 @@ struct SeatDialogPromptDescriptions {
|
||||
const char *hk_accept_action;
|
||||
const char *hk_connect_once_action;
|
||||
const char *hk_cancel_action, *hk_cancel_action_Participle;
|
||||
const char *weak_accept_action, *weak_cancel_action;
|
||||
};
|
||||
|
||||
/* In the utils subdir: print a message to the Seat which can't be
|
||||
@ -1530,10 +1530,10 @@ SeatPromptResult nullseat_confirm_ssh_host_key(
|
||||
char *keystr, SeatDialogText *text, HelpCtx helpctx,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
|
||||
SeatPromptResult nullseat_confirm_weak_crypto_primitive(
|
||||
Seat *seat, const char *algtype, const char *algname,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
|
||||
SeatPromptResult nullseat_confirm_weak_cached_hostkey(
|
||||
Seat *seat, const char *algname, const char *betteralgs,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
|
||||
const SeatDialogPromptDescriptions *nullseat_prompt_descriptions(Seat *seat);
|
||||
bool nullseat_is_never_utf8(Seat *seat);
|
||||
@ -1567,10 +1567,10 @@ SeatPromptResult console_confirm_ssh_host_key(
|
||||
char *keystr, SeatDialogText *text, HelpCtx helpctx,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
|
||||
SeatPromptResult console_confirm_weak_crypto_primitive(
|
||||
Seat *seat, const char *algtype, const char *algname,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
|
||||
SeatPromptResult console_confirm_weak_cached_hostkey(
|
||||
Seat *seat, const char *algname, const char *betteralgs,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
|
||||
StripCtrlChars *console_stripctrl_new(
|
||||
Seat *seat, BinarySink *bs_out, SeatInteractionContext sic);
|
||||
|
15
ssh.h
15
ssh.h
@ -1903,11 +1903,26 @@ void add_to_commasep(strbuf *buf, const char *data);
|
||||
void add_to_commasep_pl(strbuf *buf, ptrlen data);
|
||||
bool get_commasep_word(ptrlen *list, ptrlen *word);
|
||||
|
||||
/* Reasons why something warned by confirm_weak_crypto_primitive might
|
||||
* be considered weak */
|
||||
typedef enum WeakCryptoReason {
|
||||
WCR_BELOW_THRESHOLD, /* user has told us to consider it weak */
|
||||
WCR_TERRAPIN, /* known vulnerability CVE-2023-48795 */
|
||||
WCR_TERRAPIN_AVOIDABLE, /* same, but demoting ChaCha20 can avoid it */
|
||||
} WeakCryptoReason;
|
||||
|
||||
SeatPromptResult verify_ssh_host_key(
|
||||
InteractionReadySeat iseat, Conf *conf, const char *host, int port,
|
||||
ssh_key *key, const char *keytype, char *keystr, const char *keydisp,
|
||||
char **fingerprints, int ca_count,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
|
||||
SeatPromptResult confirm_weak_crypto_primitive(
|
||||
InteractionReadySeat iseat, const char *algtype, const char *algname,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx,
|
||||
WeakCryptoReason wcr);
|
||||
SeatPromptResult confirm_weak_cached_hostkey(
|
||||
InteractionReadySeat iseat, const char *algname, const char **betteralgs,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
|
||||
|
||||
typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache;
|
||||
ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void);
|
||||
|
@ -138,12 +138,14 @@ void ssh2_bpp_new_outgoing_crypto(
|
||||
BinaryPacketProtocol *bpp,
|
||||
const ssh_cipheralg *cipher, const void *ckey, const void *iv,
|
||||
const ssh2_macalg *mac, bool etm_mode, const void *mac_key,
|
||||
const ssh_compression_alg *compression, bool delayed_compression);
|
||||
const ssh_compression_alg *compression, bool delayed_compression,
|
||||
bool reset_sequence_number);
|
||||
void ssh2_bpp_new_incoming_crypto(
|
||||
BinaryPacketProtocol *bpp,
|
||||
const ssh_cipheralg *cipher, const void *ckey, const void *iv,
|
||||
const ssh2_macalg *mac, bool etm_mode, const void *mac_key,
|
||||
const ssh_compression_alg *compression, bool delayed_compression);
|
||||
const ssh_compression_alg *compression, bool delayed_compression,
|
||||
bool reset_sequence_number);
|
||||
|
||||
/*
|
||||
* A query method specific to the interface between ssh2transport and
|
||||
|
12
ssh/bpp2.c
12
ssh/bpp2.c
@ -106,7 +106,8 @@ void ssh2_bpp_new_outgoing_crypto(
|
||||
BinaryPacketProtocol *bpp,
|
||||
const ssh_cipheralg *cipher, const void *ckey, const void *iv,
|
||||
const ssh2_macalg *mac, bool etm_mode, const void *mac_key,
|
||||
const ssh_compression_alg *compression, bool delayed_compression)
|
||||
const ssh_compression_alg *compression, bool delayed_compression,
|
||||
bool reset_sequence_number)
|
||||
{
|
||||
struct ssh2_bpp_state *s;
|
||||
assert(bpp->vt == &ssh2_bpp_vtable);
|
||||
@ -150,6 +151,9 @@ void ssh2_bpp_new_outgoing_crypto(
|
||||
s->out.mac = NULL;
|
||||
}
|
||||
|
||||
if (reset_sequence_number)
|
||||
s->out.sequence = 0;
|
||||
|
||||
if (delayed_compression && !s->seen_userauth_success) {
|
||||
s->out.pending_compression = compression;
|
||||
s->out_comp = NULL;
|
||||
@ -174,7 +178,8 @@ void ssh2_bpp_new_incoming_crypto(
|
||||
BinaryPacketProtocol *bpp,
|
||||
const ssh_cipheralg *cipher, const void *ckey, const void *iv,
|
||||
const ssh2_macalg *mac, bool etm_mode, const void *mac_key,
|
||||
const ssh_compression_alg *compression, bool delayed_compression)
|
||||
const ssh_compression_alg *compression, bool delayed_compression,
|
||||
bool reset_sequence_number)
|
||||
{
|
||||
struct ssh2_bpp_state *s;
|
||||
assert(bpp->vt == &ssh2_bpp_vtable);
|
||||
@ -231,6 +236,9 @@ void ssh2_bpp_new_incoming_crypto(
|
||||
* start consuming the input data again. */
|
||||
s->pending_newkeys = false;
|
||||
|
||||
if (reset_sequence_number)
|
||||
s->in.sequence = 0;
|
||||
|
||||
/* And schedule a run of handle_input, in case there's already
|
||||
* input data in the queue. */
|
||||
queue_idempotent_callback(&s->bpp.ic_in_raw);
|
||||
|
104
ssh/common.c
104
ssh/common.c
@ -1085,6 +1085,110 @@ SeatPromptResult verify_ssh_host_key(
|
||||
return toret;
|
||||
}
|
||||
|
||||
SeatPromptResult confirm_weak_crypto_primitive(
|
||||
InteractionReadySeat iseat, const char *algtype, const char *algname,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx,
|
||||
WeakCryptoReason wcr)
|
||||
{
|
||||
SeatDialogText *text = seat_dialog_text_new();
|
||||
const SeatDialogPromptDescriptions *pds =
|
||||
seat_prompt_descriptions(iseat.seat);
|
||||
|
||||
seat_dialog_text_append(text, SDT_TITLE, "%s Security Alert", appname);
|
||||
|
||||
switch (wcr) {
|
||||
case WCR_BELOW_THRESHOLD:
|
||||
seat_dialog_text_append(
|
||||
text, SDT_PARA,
|
||||
"The first %s supported by the server is %s, "
|
||||
"which is below the configured warning threshold.",
|
||||
algtype, algname);
|
||||
break;
|
||||
case WCR_TERRAPIN:
|
||||
case WCR_TERRAPIN_AVOIDABLE:
|
||||
seat_dialog_text_append(
|
||||
text, SDT_PARA,
|
||||
"The %s selected for this session is %s, "
|
||||
"which, with this server, is vulnerable to the 'Terrapin' attack "
|
||||
"CVE-2023-48795, potentially allowing an attacker to modify "
|
||||
"the encrypted session.",
|
||||
algtype, algname);
|
||||
seat_dialog_text_append(
|
||||
text, SDT_PARA,
|
||||
"Upgrading, patching, or reconfiguring this SSH server is the "
|
||||
"best way to avoid this vulnerability, if possible.");
|
||||
if (wcr == WCR_TERRAPIN_AVOIDABLE) {
|
||||
seat_dialog_text_append(
|
||||
text, SDT_PARA,
|
||||
"You can also avoid this vulnerability by abandoning "
|
||||
"this connection, moving ChaCha20 to below the "
|
||||
"'warn below here' line in PuTTY's SSH cipher "
|
||||
"configuration (so that an algorithm without the "
|
||||
"vulnerability will be selected), and starting a new "
|
||||
"connection.");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
unreachable("bad WeakCryptoReason");
|
||||
}
|
||||
|
||||
/* In batch mode, we print the above information and then this
|
||||
* abort message, and stop. */
|
||||
seat_dialog_text_append(text, SDT_BATCH_ABORT, "Connection abandoned.");
|
||||
|
||||
seat_dialog_text_append(
|
||||
text, SDT_PARA, "To accept the risk and continue, %s. "
|
||||
"To abandon the connection, %s.",
|
||||
pds->weak_accept_action, pds->weak_cancel_action);
|
||||
|
||||
seat_dialog_text_append(text, SDT_PROMPT, "Continue with connection?");
|
||||
|
||||
SeatPromptResult toret = seat_confirm_weak_crypto_primitive(
|
||||
iseat, text, callback, ctx);
|
||||
seat_dialog_text_free(text);
|
||||
return toret;
|
||||
}
|
||||
|
||||
SeatPromptResult confirm_weak_cached_hostkey(
|
||||
InteractionReadySeat iseat, const char *algname, const char **betteralgs,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{
|
||||
SeatDialogText *text = seat_dialog_text_new();
|
||||
const SeatDialogPromptDescriptions *pds =
|
||||
seat_prompt_descriptions(iseat.seat);
|
||||
|
||||
seat_dialog_text_append(text, SDT_TITLE, "%s Security Alert", appname);
|
||||
|
||||
seat_dialog_text_append(
|
||||
text, SDT_PARA,
|
||||
"The first host key type we have stored for this server "
|
||||
"is %s, which is below the configured warning threshold.", algname);
|
||||
|
||||
seat_dialog_text_append(
|
||||
text, SDT_PARA,
|
||||
"The server also provides the following types of host key "
|
||||
"above the threshold, which we do not have stored:");
|
||||
|
||||
for (const char **p = betteralgs; *p; p++)
|
||||
seat_dialog_text_append(text, SDT_DISPLAY, "%s", *p);
|
||||
|
||||
/* In batch mode, we print the above information and then this
|
||||
* abort message, and stop. */
|
||||
seat_dialog_text_append(text, SDT_BATCH_ABORT, "Connection abandoned.");
|
||||
|
||||
seat_dialog_text_append(
|
||||
text, SDT_PARA, "To accept the risk and continue, %s. "
|
||||
"To abandon the connection, %s.",
|
||||
pds->weak_accept_action, pds->weak_cancel_action);
|
||||
|
||||
seat_dialog_text_append(text, SDT_PROMPT, "Continue with connection?");
|
||||
|
||||
SeatPromptResult toret = seat_confirm_weak_cached_hostkey(
|
||||
iseat, text, callback, ctx);
|
||||
seat_dialog_text_free(text);
|
||||
return toret;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------
|
||||
* Common functions shared between SSH-1 layers.
|
||||
*/
|
||||
|
@ -323,9 +323,9 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
|
||||
|
||||
/* Warn about chosen cipher if necessary. */
|
||||
if (warn) {
|
||||
s->spr = seat_confirm_weak_crypto_primitive(
|
||||
s->spr = confirm_weak_crypto_primitive(
|
||||
ppl_get_iseat(&s->ppl), "cipher", cipher_string,
|
||||
ssh1_login_dialog_callback, s);
|
||||
ssh1_login_dialog_callback, s, WCR_BELOW_THRESHOLD);
|
||||
crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
|
||||
if (spr_is_abort(s->spr)) {
|
||||
ssh_spr_close(s->ppl.ssh, s->spr, "cipher warning");
|
||||
|
@ -98,11 +98,11 @@ void mainchan_terminal_size(mainchan *mc, int width, int height) {}
|
||||
/* Seat functions to ensure we don't get choosy about crypto - as the
|
||||
* server, it's not up to us to give user warnings */
|
||||
static SeatPromptResult server_confirm_weak_crypto_primitive(
|
||||
Seat *seat, const char *algtype, const char *algname,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{ return SPR_OK; }
|
||||
static SeatPromptResult server_confirm_weak_cached_hostkey(
|
||||
Seat *seat, const char *algname, const char *betteralgs,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{ return SPR_OK; }
|
||||
|
||||
|
451
ssh/transport2.c
451
ssh/transport2.c
@ -27,6 +27,18 @@ const static ssh2_macalg *const buggymacs[] = {
|
||||
&ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5
|
||||
};
|
||||
|
||||
const static ptrlen ext_info_c = PTRLEN_DECL_LITERAL("ext-info-c");
|
||||
const static ptrlen ext_info_s = PTRLEN_DECL_LITERAL("ext-info-s");
|
||||
const static ptrlen kex_strict_c =
|
||||
PTRLEN_DECL_LITERAL("kex-strict-c-v00@openssh.com");
|
||||
const static ptrlen kex_strict_s =
|
||||
PTRLEN_DECL_LITERAL("kex-strict-s-v00@openssh.com");
|
||||
|
||||
/* Pointer value to store in s->weak_algorithms_consented_to to
|
||||
* indicate that the user has accepted the risk of the Terrapin
|
||||
* attack */
|
||||
static const char terrapin_weakness[1];
|
||||
|
||||
static ssh_compressor *ssh_comp_none_init(void)
|
||||
{
|
||||
return NULL;
|
||||
@ -81,6 +93,9 @@ static void ssh2_transport_set_max_data_size(struct ssh2_transport_state *s);
|
||||
static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def);
|
||||
static void ssh2_transport_higher_layer_packet_callback(void *context);
|
||||
static void ssh2_transport_final_output(PacketProtocolLayer *ppl);
|
||||
static const char *terrapin_vulnerable(
|
||||
bool strict_kex, const transport_direction *d);
|
||||
static bool try_to_avoid_terrapin(const struct ssh2_transport_state *s);
|
||||
|
||||
static const PacketProtocolLayerVtable ssh2_transport_vtable = {
|
||||
.free = ssh2_transport_free,
|
||||
@ -102,7 +117,7 @@ static bool ssh2_transport_timer_update(struct ssh2_transport_state *s,
|
||||
unsigned long rekey_time);
|
||||
static SeatPromptResult ssh2_transport_confirm_weak_crypto_primitive(
|
||||
struct ssh2_transport_state *s, const char *type, const char *name,
|
||||
const void *alg);
|
||||
const void *alg, WeakCryptoReason wcr);
|
||||
|
||||
static const char *const kexlist_descr[NKEXLIST] = {
|
||||
"key exchange algorithm",
|
||||
@ -462,6 +477,31 @@ static bool ssh2_transport_filter_queue(struct ssh2_transport_state *s)
|
||||
{
|
||||
PktIn *pktin;
|
||||
|
||||
if (!s->enabled_incoming_crypto) {
|
||||
/*
|
||||
* Record the fact that we've seen any non-KEXINIT packet at
|
||||
* the head of our queue.
|
||||
*
|
||||
* This enables us to check later that the initial incoming
|
||||
* KEXINIT was the very first packet, if scanning the KEXINITs
|
||||
* turns out to enable strict-kex mode.
|
||||
*/
|
||||
PktIn *pktin = pq_peek(s->ppl.in_pq);
|
||||
if (pktin && pktin->type != SSH2_MSG_KEXINIT)
|
||||
s->seen_non_kexinit = true;
|
||||
|
||||
if (s->strict_kex) {
|
||||
/*
|
||||
* Also, if we're already in strict-KEX mode and haven't
|
||||
* turned on crypto yet, don't do any actual filtering.
|
||||
* This ensures that extraneous packets _after_ the
|
||||
* KEXINIT will go to the main coroutine, which will
|
||||
* complain about them.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
while (1) {
|
||||
if (ssh2_common_filter_queue(&s->ppl))
|
||||
return true;
|
||||
@ -937,10 +977,13 @@ static void ssh2_write_kexinit_lists(
|
||||
add_to_commasep_pl(list, kexlists[i].algs[j].name);
|
||||
}
|
||||
if (i == KEXLIST_KEX && first_time) {
|
||||
if (our_hostkeys) /* we're the server */
|
||||
add_to_commasep(list, "ext-info-s");
|
||||
else /* we're the client */
|
||||
add_to_commasep(list, "ext-info-c");
|
||||
if (our_hostkeys) { /* we're the server */
|
||||
add_to_commasep_pl(list, ext_info_s);
|
||||
add_to_commasep_pl(list, kex_strict_s);
|
||||
} else { /* we're the client */
|
||||
add_to_commasep_pl(list, ext_info_c);
|
||||
add_to_commasep_pl(list, kex_strict_c);
|
||||
}
|
||||
}
|
||||
put_stringsb(pktout, list);
|
||||
}
|
||||
@ -955,15 +998,37 @@ struct server_hostkeys {
|
||||
size_t n, size;
|
||||
};
|
||||
|
||||
static bool ssh2_scan_kexinits(
|
||||
ptrlen client_kexinit, ptrlen server_kexinit,
|
||||
static bool kexinit_keyword_found(ptrlen list, ptrlen keyword)
|
||||
{
|
||||
for (ptrlen word; get_commasep_word(&list, &word) ;)
|
||||
if (ptrlen_eq_ptrlen(word, keyword))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef struct ScanKexinitsResult {
|
||||
bool success;
|
||||
|
||||
/* only if success is false */
|
||||
enum {
|
||||
SKR_INCOMPLETE,
|
||||
SKR_UNKNOWN_ID,
|
||||
SKR_NO_AGREEMENT,
|
||||
} error;
|
||||
|
||||
const char *kind; /* what kind of thing did we fail to sort out? */
|
||||
ptrlen desc; /* and what was it? or what was the available list? */
|
||||
} ScanKexinitsResult;
|
||||
|
||||
static ScanKexinitsResult ssh2_scan_kexinits(
|
||||
ptrlen client_kexinit, ptrlen server_kexinit, bool we_are_server,
|
||||
struct kexinit_algorithm_list kexlists[NKEXLIST],
|
||||
const ssh_kex **kex_alg, const ssh_keyalg **hostkey_alg,
|
||||
transport_direction *cs, transport_direction *sc,
|
||||
bool *warn_kex, bool *warn_hk, bool *warn_cscipher, bool *warn_sccipher,
|
||||
Ssh *ssh, bool *ignore_guess_cs_packet, bool *ignore_guess_sc_packet,
|
||||
bool *ignore_guess_cs_packet, bool *ignore_guess_sc_packet,
|
||||
struct server_hostkeys *server_hostkeys, unsigned *hkflags,
|
||||
bool *can_send_ext_info)
|
||||
bool *can_send_ext_info, bool first_time, bool *strict_kex)
|
||||
{
|
||||
BinarySource client[1], server[1];
|
||||
int i;
|
||||
@ -990,11 +1055,10 @@ static bool ssh2_scan_kexinits(
|
||||
clists[i] = get_string(client);
|
||||
slists[i] = get_string(server);
|
||||
if (get_err(client) || get_err(server)) {
|
||||
/* Report a better error than the spurious "Couldn't
|
||||
* agree" that we'd generate if we pressed on regardless
|
||||
* and treated the empty get_string() result as genuine */
|
||||
ssh_proto_error(ssh, "KEXINIT packet was incomplete");
|
||||
return false;
|
||||
ScanKexinitsResult skr = {
|
||||
.success = false, .error = SKR_INCOMPLETE,
|
||||
};
|
||||
return skr;
|
||||
}
|
||||
|
||||
for (cfirst = true, clist = clists[i];
|
||||
@ -1042,10 +1106,11 @@ static bool ssh2_scan_kexinits(
|
||||
* produce a reasonably useful message instead of an
|
||||
* assertion failure.
|
||||
*/
|
||||
ssh_sw_abort(ssh, "Selected %s \"%.*s\" does not correspond to "
|
||||
"any supported algorithm",
|
||||
kexlist_descr[i], PTRLEN_PRINTF(found));
|
||||
return false;
|
||||
ScanKexinitsResult skr = {
|
||||
.success = false, .error = SKR_UNKNOWN_ID,
|
||||
.kind = kexlist_descr[i], .desc = found,
|
||||
};
|
||||
return skr;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1100,9 +1165,11 @@ static bool ssh2_scan_kexinits(
|
||||
/*
|
||||
* Otherwise, any match failure _is_ a fatal error.
|
||||
*/
|
||||
ssh_sw_abort(ssh, "Couldn't agree a %s (available: %.*s)",
|
||||
kexlist_descr[i], PTRLEN_PRINTF(slists[i]));
|
||||
return false;
|
||||
ScanKexinitsResult skr = {
|
||||
.success = false, .error = SKR_UNKNOWN_ID,
|
||||
.kind = kexlist_descr[i], .desc = slists[i],
|
||||
};
|
||||
return skr;
|
||||
}
|
||||
|
||||
switch (i) {
|
||||
@ -1165,16 +1232,18 @@ static bool ssh2_scan_kexinits(
|
||||
/*
|
||||
* Check whether the other side advertised support for EXT_INFO.
|
||||
*/
|
||||
{
|
||||
ptrlen extinfo_advert =
|
||||
(server_hostkeys ? PTRLEN_LITERAL("ext-info-c") :
|
||||
PTRLEN_LITERAL("ext-info-s"));
|
||||
ptrlen list = (server_hostkeys ? clists[KEXLIST_KEX] :
|
||||
slists[KEXLIST_KEX]);
|
||||
for (ptrlen word; get_commasep_word(&list, &word) ;)
|
||||
if (ptrlen_eq_ptrlen(word, extinfo_advert))
|
||||
*can_send_ext_info = true;
|
||||
}
|
||||
if (kexinit_keyword_found(
|
||||
we_are_server ? clists[KEXLIST_KEX] : slists[KEXLIST_KEX],
|
||||
we_are_server ? ext_info_c : ext_info_s))
|
||||
*can_send_ext_info = true;
|
||||
|
||||
/*
|
||||
* Check whether the other side advertised support for kex-strict.
|
||||
*/
|
||||
if (first_time && kexinit_keyword_found(
|
||||
we_are_server ? clists[KEXLIST_KEX] : slists[KEXLIST_KEX],
|
||||
we_are_server ? kex_strict_c : kex_strict_s))
|
||||
*strict_kex = true;
|
||||
|
||||
if (server_hostkeys) {
|
||||
/*
|
||||
@ -1196,7 +1265,33 @@ static bool ssh2_scan_kexinits(
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
ScanKexinitsResult skr = { .success = true };
|
||||
return skr;
|
||||
}
|
||||
|
||||
static void ssh2_report_scan_kexinits_error(Ssh *ssh, ScanKexinitsResult skr)
|
||||
{
|
||||
assert(!skr.success);
|
||||
|
||||
switch (skr.error) {
|
||||
case SKR_INCOMPLETE:
|
||||
/* Report a better error than the spurious "Couldn't
|
||||
* agree" that we'd generate if we pressed on regardless
|
||||
* and treated the empty get_string() result as genuine */
|
||||
ssh_proto_error(ssh, "KEXINIT packet was incomplete");
|
||||
break;
|
||||
case SKR_UNKNOWN_ID:
|
||||
ssh_sw_abort(ssh, "Selected %s \"%.*s\" does not correspond to "
|
||||
"any supported algorithm",
|
||||
skr.kind, PTRLEN_PRINTF(skr.desc));
|
||||
break;
|
||||
case SKR_NO_AGREEMENT:
|
||||
ssh_sw_abort(ssh, "Couldn't agree a %s (available: %.*s)",
|
||||
skr.kind, PTRLEN_PRINTF(skr.desc));
|
||||
break;
|
||||
default:
|
||||
unreachable("bad ScanKexinitsResult");
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool delay_outgoing_kexinit(struct ssh2_transport_state *s)
|
||||
@ -1239,10 +1334,26 @@ static void filter_outgoing_kexinit(struct ssh2_transport_state *s)
|
||||
strbuf_clear(out);
|
||||
ptrlen olist = get_string(osrc), ilist = get_string(isrc);
|
||||
for (ptrlen oword; get_commasep_word(&olist, &oword) ;) {
|
||||
ptrlen searchword = oword;
|
||||
ptrlen ilist_copy = ilist;
|
||||
|
||||
/*
|
||||
* Special case: the kex_strict keywords are
|
||||
* asymmetrically named, so if we're contemplating
|
||||
* including one of them in our filtered KEXINIT, we
|
||||
* should search the other side's KEXINIT for the _other_
|
||||
* one, not the same one.
|
||||
*/
|
||||
if (i == KEXLIST_KEX) {
|
||||
if (ptrlen_eq_ptrlen(oword, kex_strict_c))
|
||||
searchword = kex_strict_s;
|
||||
else if (ptrlen_eq_ptrlen(oword, kex_strict_s))
|
||||
searchword = kex_strict_c;
|
||||
}
|
||||
|
||||
bool add = false;
|
||||
for (ptrlen iword; get_commasep_word(&ilist_copy, &iword) ;) {
|
||||
if (ptrlen_eq_ptrlen(oword, iword)) {
|
||||
if (ptrlen_eq_ptrlen(searchword, iword)) {
|
||||
/* Found this word in the incoming list. */
|
||||
add = true;
|
||||
break;
|
||||
@ -1461,15 +1572,32 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
|
||||
{
|
||||
struct server_hostkeys hks = { NULL, 0, 0 };
|
||||
|
||||
if (!ssh2_scan_kexinits(
|
||||
ScanKexinitsResult skr = ssh2_scan_kexinits(
|
||||
ptrlen_from_strbuf(s->client_kexinit),
|
||||
ptrlen_from_strbuf(s->server_kexinit),
|
||||
ptrlen_from_strbuf(s->server_kexinit), s->ssc != NULL,
|
||||
s->kexlists, &s->kex_alg, &s->hostkey_alg, s->cstrans,
|
||||
s->sctrans, &s->warn_kex, &s->warn_hk, &s->warn_cscipher,
|
||||
&s->warn_sccipher, s->ppl.ssh, NULL, &s->ignorepkt, &hks,
|
||||
&s->hkflags, &s->can_send_ext_info)) {
|
||||
&s->warn_sccipher, NULL, &s->ignorepkt, &hks,
|
||||
&s->hkflags, &s->can_send_ext_info, !s->got_session_id,
|
||||
&s->strict_kex);
|
||||
|
||||
if (!skr.success) {
|
||||
sfree(hks.indices);
|
||||
return; /* false means a fatal error function was called */
|
||||
ssh2_report_scan_kexinits_error(s->ppl.ssh, skr);
|
||||
return; /* we just called a fatal error function */
|
||||
}
|
||||
|
||||
/*
|
||||
* If we've just turned on strict kex mode, say so, and
|
||||
* retrospectively fault any pre-KEXINIT extraneous packets.
|
||||
*/
|
||||
if (!s->got_session_id && s->strict_kex) {
|
||||
ppl_logevent("Enabling strict key exchange semantics");
|
||||
if (s->seen_non_kexinit) {
|
||||
ssh_proto_error(s->ppl.ssh, "Received a packet before KEXINIT "
|
||||
"in strict-kex mode");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1499,7 +1627,8 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
|
||||
|
||||
if (s->warn_kex) {
|
||||
s->spr = ssh2_transport_confirm_weak_crypto_primitive(
|
||||
s, "key-exchange algorithm", s->kex_alg->name, s->kex_alg);
|
||||
s, "key-exchange algorithm", s->kex_alg->name, s->kex_alg,
|
||||
WCR_BELOW_THRESHOLD);
|
||||
crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
|
||||
if (spr_is_abort(s->spr)) {
|
||||
ssh_spr_close(s->ppl.ssh, s->spr, "kex warning");
|
||||
@ -1509,7 +1638,8 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
|
||||
|
||||
if (s->warn_hk) {
|
||||
int j, k;
|
||||
char *betteralgs;
|
||||
const char **betteralgs = NULL;
|
||||
size_t nbetter = 0, bettersize = 0;
|
||||
|
||||
/*
|
||||
* Change warning box wording depending on why we chose a
|
||||
@ -1518,7 +1648,6 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
|
||||
* could usefully cross-certify. Otherwise, use the same
|
||||
* standard wording as any other weak crypto primitive.
|
||||
*/
|
||||
betteralgs = NULL;
|
||||
for (j = 0; j < s->n_uncert_hostkeys; j++) {
|
||||
const struct ssh_signkey_with_user_pref_id *hktype =
|
||||
&ssh2_hostkey_algs[s->uncert_hostkeys[j]];
|
||||
@ -1533,19 +1662,16 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
|
||||
}
|
||||
}
|
||||
if (better) {
|
||||
if (betteralgs) {
|
||||
char *old_ba = betteralgs;
|
||||
betteralgs = dupcat(betteralgs, ",", hktype->alg->ssh_id);
|
||||
sfree(old_ba);
|
||||
} else {
|
||||
betteralgs = dupstr(hktype->alg->ssh_id);
|
||||
}
|
||||
sgrowarray(betteralgs, bettersize, nbetter);
|
||||
betteralgs[nbetter++] = hktype->alg->ssh_id;
|
||||
}
|
||||
}
|
||||
if (betteralgs) {
|
||||
/* Use the special warning prompt that lets us provide
|
||||
* a list of better algorithms */
|
||||
s->spr = seat_confirm_weak_cached_hostkey(
|
||||
sgrowarray(betteralgs, bettersize, nbetter);
|
||||
betteralgs[nbetter] = NULL;
|
||||
s->spr = confirm_weak_cached_hostkey(
|
||||
ppl_get_iseat(&s->ppl), s->hostkey_alg->ssh_id, betteralgs,
|
||||
ssh2_transport_dialog_callback, s);
|
||||
sfree(betteralgs);
|
||||
@ -1554,7 +1680,7 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
|
||||
* warning prompt */
|
||||
s->spr = ssh2_transport_confirm_weak_crypto_primitive(
|
||||
s, "host key type", s->hostkey_alg->ssh_id,
|
||||
s->hostkey_alg);
|
||||
s->hostkey_alg, WCR_BELOW_THRESHOLD);
|
||||
}
|
||||
crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
|
||||
if (spr_is_abort(s->spr)) {
|
||||
@ -1566,7 +1692,7 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
|
||||
if (s->warn_cscipher) {
|
||||
s->spr = ssh2_transport_confirm_weak_crypto_primitive(
|
||||
s, "client-to-server cipher", s->out.cipher->ssh2_id,
|
||||
s->out.cipher);
|
||||
s->out.cipher, WCR_BELOW_THRESHOLD);
|
||||
crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
|
||||
if (spr_is_abort(s->spr)) {
|
||||
ssh_spr_close(s->ppl.ssh, s->spr, "cipher warning");
|
||||
@ -1577,7 +1703,7 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
|
||||
if (s->warn_sccipher) {
|
||||
s->spr = ssh2_transport_confirm_weak_crypto_primitive(
|
||||
s, "server-to-client cipher", s->in.cipher->ssh2_id,
|
||||
s->in.cipher);
|
||||
s->in.cipher, WCR_BELOW_THRESHOLD);
|
||||
crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
|
||||
if (spr_is_abort(s->spr)) {
|
||||
ssh_spr_close(s->ppl.ssh, s->spr, "cipher warning");
|
||||
@ -1585,6 +1711,46 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
s->terrapin.csvuln = terrapin_vulnerable(s->strict_kex, s->cstrans);
|
||||
s->terrapin.scvuln = terrapin_vulnerable(s->strict_kex, s->sctrans);
|
||||
s->terrapin.wcr = WCR_TERRAPIN;
|
||||
|
||||
if (s->terrapin.csvuln || s->terrapin.scvuln) {
|
||||
ppl_logevent("SSH connection is vulnerable to 'Terrapin' attack "
|
||||
"(CVE-2023-48795)");
|
||||
if (try_to_avoid_terrapin(s))
|
||||
s->terrapin.wcr = WCR_TERRAPIN_AVOIDABLE;
|
||||
}
|
||||
|
||||
if (s->terrapin.csvuln) {
|
||||
s->spr = ssh2_transport_confirm_weak_crypto_primitive(
|
||||
s, "client-to-server cipher", s->terrapin.csvuln,
|
||||
terrapin_weakness, s->terrapin.wcr);
|
||||
crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
|
||||
if (spr_is_abort(s->spr)) {
|
||||
ssh_spr_close(s->ppl.ssh, s->spr, "vulnerability warning");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (s->terrapin.scvuln) {
|
||||
s->spr = ssh2_transport_confirm_weak_crypto_primitive(
|
||||
s, "server-to-client cipher", s->terrapin.scvuln,
|
||||
terrapin_weakness, s->terrapin.wcr);
|
||||
crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
|
||||
if (spr_is_abort(s->spr)) {
|
||||
ssh_spr_close(s->ppl.ssh, s->spr, "vulnerability warning");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (s->terrapin.csvuln || s->terrapin.scvuln) {
|
||||
ppl_logevent("Continuing despite 'Terrapin' vulnerability, "
|
||||
"at user request");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If the other side has sent an initial key exchange packet that
|
||||
* we must treat as a wrong guess, wait for it, and discard it.
|
||||
@ -1667,7 +1833,9 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
|
||||
s->ppl.bpp,
|
||||
s->out.cipher, cipher_key->u, cipher_iv->u,
|
||||
s->out.mac, s->out.etm_mode, mac_key->u,
|
||||
s->out.comp, s->out.comp_delayed);
|
||||
s->out.comp, s->out.comp_delayed,
|
||||
s->strict_kex);
|
||||
s->enabled_outgoing_crypto = true;
|
||||
|
||||
strbuf_free(cipher_key);
|
||||
strbuf_free(cipher_iv);
|
||||
@ -1759,7 +1927,9 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
|
||||
s->ppl.bpp,
|
||||
s->in.cipher, cipher_key->u, cipher_iv->u,
|
||||
s->in.mac, s->in.etm_mode, mac_key->u,
|
||||
s->in.comp, s->in.comp_delayed);
|
||||
s->in.comp, s->in.comp_delayed,
|
||||
s->strict_kex);
|
||||
s->enabled_incoming_crypto = true;
|
||||
|
||||
strbuf_free(cipher_key);
|
||||
strbuf_free(cipher_iv);
|
||||
@ -2384,20 +2554,21 @@ static int ca_blob_compare(void *av, void *bv)
|
||||
}
|
||||
|
||||
/*
|
||||
* Wrapper on seat_confirm_weak_crypto_primitive(), which uses the
|
||||
* Wrapper on confirm_weak_crypto_primitive(), which uses the
|
||||
* tree234 s->weak_algorithms_consented_to to ensure we ask at most
|
||||
* once about any given crypto primitive.
|
||||
*/
|
||||
static SeatPromptResult ssh2_transport_confirm_weak_crypto_primitive(
|
||||
struct ssh2_transport_state *s, const char *type, const char *name,
|
||||
const void *alg)
|
||||
const void *alg, WeakCryptoReason wcr)
|
||||
{
|
||||
if (find234(s->weak_algorithms_consented_to, (void *)alg, NULL))
|
||||
return SPR_OK;
|
||||
add234(s->weak_algorithms_consented_to, (void *)alg);
|
||||
|
||||
return seat_confirm_weak_crypto_primitive(
|
||||
ppl_get_iseat(&s->ppl), type, name, ssh2_transport_dialog_callback, s);
|
||||
return confirm_weak_crypto_primitive(
|
||||
ppl_get_iseat(&s->ppl), type, name, ssh2_transport_dialog_callback,
|
||||
s, wcr);
|
||||
}
|
||||
|
||||
static size_t ssh2_transport_queued_data_size(PacketProtocolLayer *ppl)
|
||||
@ -2416,3 +2587,167 @@ static void ssh2_transport_final_output(PacketProtocolLayer *ppl)
|
||||
|
||||
ssh_ppl_final_output(s->higher_layer);
|
||||
}
|
||||
|
||||
/* Check the settings for a transport direction to see if they're
|
||||
* vulnerable to the Terrapin attack, aka CVE-2023-48795. If so,
|
||||
* return a string describing the vulnerable thing. */
|
||||
static const char *terrapin_vulnerable(
|
||||
bool strict_kex, const transport_direction *d)
|
||||
{
|
||||
/*
|
||||
* Strict kex mode eliminates the vulnerability. (That's what it's
|
||||
* for.)
|
||||
*/
|
||||
if (strict_kex)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* ChaCha20-Poly1305 is vulnerable and perfectly exploitable.
|
||||
*/
|
||||
if (d->cipher == &ssh2_chacha20_poly1305)
|
||||
return "ChaCha20-Poly1305";
|
||||
|
||||
/*
|
||||
* CBC-mode ciphers with OpenSSH's ETM modification are vulnerable
|
||||
* and probabilistically exploitable.
|
||||
*/
|
||||
if (d->etm_mode && (d->cipher->flags & SSH_CIPHER_IS_CBC))
|
||||
return "a CBC-mode cipher in OpenSSH ETM mode";
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when we've detected that at least one transport direction
|
||||
* has the Terrapin vulnerability.
|
||||
*
|
||||
* Before we report it, try to replay what would have happened if the
|
||||
* user had reconfigured their cipher settings to demote
|
||||
* ChaCha20+Poly1305 to below the warning threshold. If that would
|
||||
* have avoided the vulnerability, we should say so in the dialog box.
|
||||
*
|
||||
* This is basically the only change in PuTTY's configuration that has
|
||||
* a chance of avoiding the problem. Terrapin affects the modified
|
||||
* binary packet protocol used with ChaCha20+Poly1305, and also
|
||||
* CBC-mode ciphers in ETM mode. But PuTTY unconditionally offers the
|
||||
* ETM mode of each MAC _after_ the non-ETM mode. So the latter case
|
||||
* can only come up if the server has been configured to _only_ permit
|
||||
* the ETM modes of those MACs, which means there's nothing we can do
|
||||
* anyway.
|
||||
*/
|
||||
static bool try_to_avoid_terrapin(const struct ssh2_transport_state *s)
|
||||
{
|
||||
bool avoidable = false;
|
||||
|
||||
strbuf *alt_client_kexinit = strbuf_new();
|
||||
Conf *alt_conf = conf_copy(s->conf);
|
||||
struct kexinit_algorithm_list alt_kexlists[NKEXLIST];
|
||||
memset(alt_kexlists, 0, sizeof(alt_kexlists));
|
||||
|
||||
/*
|
||||
* We only bother doing this if we're the client, because Uppity
|
||||
* can't present a dialog box anyway.
|
||||
*/
|
||||
if (s->ssc)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Demote CIPHER_CHACHA20 to just below CIPHER_WARN, if it was
|
||||
* previously above it. If not, don't do anything - we don't want
|
||||
* to _promote_ it.
|
||||
*/
|
||||
int ccp_pos_now = -1, ccp_pos_wanted = -1;
|
||||
for (int i = 0; i < CIPHER_MAX; i++) {
|
||||
switch (conf_get_int_int(alt_conf, CONF_ssh_cipherlist,
|
||||
i)) {
|
||||
case CIPHER_CHACHA20:
|
||||
ccp_pos_now = i;
|
||||
break;
|
||||
case CIPHER_WARN:
|
||||
ccp_pos_wanted = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ccp_pos_now < 0 || ccp_pos_wanted < 0)
|
||||
goto out; /* shouldn't ever happen: didn't find the two entries */
|
||||
if (ccp_pos_now >= ccp_pos_wanted)
|
||||
goto out; /* ChaCha20 is already demoted and it didn't help */
|
||||
while (ccp_pos_now < ccp_pos_wanted) {
|
||||
int cnext = conf_get_int_int(alt_conf, CONF_ssh_cipherlist,
|
||||
ccp_pos_now + 1);
|
||||
conf_set_int_int(alt_conf, CONF_ssh_cipherlist,
|
||||
ccp_pos_now, cnext);
|
||||
ccp_pos_now++;
|
||||
}
|
||||
conf_set_int_int(alt_conf, CONF_ssh_cipherlist,
|
||||
ccp_pos_now + 1, CIPHER_CHACHA20);
|
||||
|
||||
/*
|
||||
* Make the outgoing KEXINIT we would have made using this
|
||||
* configuration.
|
||||
*/
|
||||
put_byte(alt_client_kexinit, SSH2_MSG_KEXINIT);
|
||||
put_padding(alt_client_kexinit, 16, 'x'); /* fake random padding */
|
||||
ssh2_write_kexinit_lists(
|
||||
BinarySink_UPCAST(alt_client_kexinit), alt_kexlists, alt_conf,
|
||||
s->ssc, s->ppl.remote_bugs, s->savedhost, s->savedport, s->hostkey_alg,
|
||||
s->thc, s->host_cas, s->hostkeys, s->nhostkeys, !s->got_session_id,
|
||||
s->can_gssapi_keyex,
|
||||
s->gss_kex_used && !s->need_gss_transient_hostkey);
|
||||
put_bool(alt_client_kexinit, false); /* guess packet follows */
|
||||
put_uint32(alt_client_kexinit, 0); /* reserved */
|
||||
|
||||
/*
|
||||
* Re-analyse the incoming KEXINIT with respect to this one, to
|
||||
* see what we'd have decided on.
|
||||
*/
|
||||
transport_direction cstrans, sctrans;
|
||||
bool warn_kex, warn_hk, warn_cscipher, warn_sccipher;
|
||||
bool can_send_ext_info = false, strict_kex = false;
|
||||
unsigned hkflags;
|
||||
const ssh_kex *kex_alg;
|
||||
const ssh_keyalg *hostkey_alg;
|
||||
|
||||
ScanKexinitsResult skr = ssh2_scan_kexinits(
|
||||
ptrlen_from_strbuf(alt_client_kexinit),
|
||||
ptrlen_from_strbuf(s->server_kexinit),
|
||||
s->ssc != NULL, alt_kexlists, &kex_alg, &hostkey_alg,
|
||||
&cstrans, &sctrans,
|
||||
&warn_kex, &warn_hk, &warn_cscipher, &warn_sccipher, NULL, NULL, NULL,
|
||||
&hkflags, &can_send_ext_info, !s->got_session_id, &strict_kex);
|
||||
if (!skr.success) /* something else would have gone wrong */
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Reject this as an alternative solution if any of the warn flags
|
||||
* has got worse, or if there's still anything
|
||||
* Terrapin-vulnerable.
|
||||
*/
|
||||
if (warn_kex > s->warn_kex)
|
||||
goto out;
|
||||
if (warn_hk > s->warn_hk)
|
||||
goto out;
|
||||
if (warn_cscipher > s->warn_cscipher)
|
||||
goto out;
|
||||
if (warn_sccipher > s->warn_sccipher)
|
||||
goto out;
|
||||
if (terrapin_vulnerable(strict_kex, &cstrans))
|
||||
goto out;
|
||||
if (terrapin_vulnerable(strict_kex, &sctrans))
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Success! The vulnerability could have been avoided by this Conf
|
||||
* tweak, and we should tell the user so.
|
||||
*/
|
||||
avoidable = true;
|
||||
|
||||
out:
|
||||
|
||||
for (size_t i = 0; i < NKEXLIST; i++)
|
||||
sfree(alt_kexlists[i].algs);
|
||||
strbuf_free(alt_client_kexinit);
|
||||
conf_free(alt_conf);
|
||||
|
||||
return avoidable;
|
||||
}
|
||||
|
@ -180,6 +180,10 @@ struct ssh2_transport_state {
|
||||
|
||||
int nbits, pbits;
|
||||
bool warn_kex, warn_hk, warn_cscipher, warn_sccipher;
|
||||
struct {
|
||||
const char *csvuln, *scvuln;
|
||||
WeakCryptoReason wcr;
|
||||
} terrapin;
|
||||
mp_int *p, *g, *e, *f;
|
||||
strbuf *ebuf, *fbuf;
|
||||
strbuf *kex_shared_secret;
|
||||
@ -202,6 +206,8 @@ struct ssh2_transport_state {
|
||||
bool warned_about_no_gss_transient_hostkey;
|
||||
bool got_session_id;
|
||||
bool can_send_ext_info, post_newkeys_ext_info;
|
||||
bool strict_kex, enabled_outgoing_crypto, enabled_incoming_crypto;
|
||||
bool seen_non_kexinit;
|
||||
SeatPromptResult spr;
|
||||
bool guessok;
|
||||
bool ignorepkt;
|
||||
|
@ -27,11 +27,11 @@ SeatPromptResult nullseat_confirm_ssh_host_key(
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); }
|
||||
SeatPromptResult nullseat_confirm_weak_crypto_primitive(
|
||||
Seat *seat, const char *algtype, const char *algname,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); }
|
||||
SeatPromptResult nullseat_confirm_weak_cached_hostkey(
|
||||
Seat *seat, const char *algname, const char *betteralgs,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); }
|
||||
bool nullseat_is_never_utf8(Seat *seat) { return false; }
|
||||
@ -61,6 +61,8 @@ const SeatDialogPromptDescriptions *nullseat_prompt_descriptions(Seat *seat)
|
||||
.hk_connect_once_action = "",
|
||||
.hk_cancel_action = "",
|
||||
.hk_cancel_action_Participle = "",
|
||||
.weak_accept_action = "",
|
||||
.weak_cancel_action = "",
|
||||
};
|
||||
return &descs;
|
||||
}
|
||||
|
@ -102,20 +102,18 @@ static int block_and_read(int fd, void *buf, size_t len)
|
||||
return ret;
|
||||
}
|
||||
|
||||
SeatPromptResult console_confirm_ssh_host_key(
|
||||
Seat *seat, const char *host, int port, const char *keytype,
|
||||
char *keystr, SeatDialogText *text, HelpCtx helpctx,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
/*
|
||||
* Helper function to print the message from a SeatDialogText. Returns
|
||||
* the final prompt to print on the input line, or NULL if a
|
||||
* batch-mode abort is needed. In the latter case it will have printed
|
||||
* the abort text already.
|
||||
*/
|
||||
static const char *console_print_seatdialogtext(SeatDialogText *text)
|
||||
{
|
||||
char line[32];
|
||||
struct termios cf;
|
||||
const char *prompt = NULL;
|
||||
|
||||
stdio_sink errsink[1];
|
||||
stdio_sink_init(errsink, stderr);
|
||||
|
||||
premsg(&cf);
|
||||
|
||||
for (SeatDialogTextItem *item = text->items,
|
||||
*end = item+text->nitems; item < end; item++) {
|
||||
switch (item->type) {
|
||||
@ -135,8 +133,7 @@ SeatPromptResult console_confirm_ssh_host_key(
|
||||
if (console_batch_mode) {
|
||||
fprintf(stderr, "%s\n", item->text);
|
||||
fflush(stderr);
|
||||
postmsg(&cf);
|
||||
return SPR_SW_ABORT("Cannot confirm a host key in batch mode");
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
case SDT_PROMPT:
|
||||
@ -146,7 +143,26 @@ SeatPromptResult console_confirm_ssh_host_key(
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert(prompt); /* something in the SeatDialogText should have set this */
|
||||
return prompt;
|
||||
}
|
||||
|
||||
SeatPromptResult console_confirm_ssh_host_key(
|
||||
Seat *seat, const char *host, int port, const char *keytype,
|
||||
char *keystr, SeatDialogText *text, HelpCtx helpctx,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{
|
||||
char line[32];
|
||||
struct termios cf;
|
||||
|
||||
premsg(&cf);
|
||||
|
||||
const char *prompt = console_print_seatdialogtext(text);
|
||||
if (!prompt) {
|
||||
postmsg(&cf);
|
||||
return SPR_SW_ABORT("Cannot confirm a host key in batch mode");
|
||||
}
|
||||
|
||||
while (true) {
|
||||
fprintf(stderr,
|
||||
@ -202,23 +218,22 @@ SeatPromptResult console_confirm_ssh_host_key(
|
||||
}
|
||||
|
||||
SeatPromptResult console_confirm_weak_crypto_primitive(
|
||||
Seat *seat, const char *algtype, const char *algname,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{
|
||||
char line[32];
|
||||
struct termios cf;
|
||||
|
||||
premsg(&cf);
|
||||
fprintf(stderr, weakcrypto_msg_common_fmt, algtype, algname);
|
||||
|
||||
if (console_batch_mode) {
|
||||
fputs(console_abandoned_msg, stderr);
|
||||
const char *prompt = console_print_seatdialogtext(text);
|
||||
if (!prompt) {
|
||||
postmsg(&cf);
|
||||
return SPR_SW_ABORT("Cannot confirm a weak crypto primitive "
|
||||
"in batch mode");
|
||||
}
|
||||
|
||||
fputs(console_continue_prompt, stderr);
|
||||
fprintf(stderr, "%s (y/n) ", prompt);
|
||||
fflush(stderr);
|
||||
|
||||
{
|
||||
@ -244,23 +259,22 @@ SeatPromptResult console_confirm_weak_crypto_primitive(
|
||||
}
|
||||
|
||||
SeatPromptResult console_confirm_weak_cached_hostkey(
|
||||
Seat *seat, const char *algname, const char *betteralgs,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{
|
||||
char line[32];
|
||||
struct termios cf;
|
||||
|
||||
premsg(&cf);
|
||||
fprintf(stderr, weakhk_msg_common_fmt, algname, betteralgs);
|
||||
|
||||
if (console_batch_mode) {
|
||||
fputs(console_abandoned_msg, stderr);
|
||||
const char *prompt = console_print_seatdialogtext(text);
|
||||
if (!prompt) {
|
||||
postmsg(&cf);
|
||||
return SPR_SW_ABORT("Cannot confirm a weak cached host key "
|
||||
"in batch mode");
|
||||
}
|
||||
|
||||
fputs(console_continue_prompt, stderr);
|
||||
fprintf(stderr, "%s (y/n) ", prompt);
|
||||
fflush(stderr);
|
||||
|
||||
{
|
||||
|
118
unix/dialog.c
118
unix/dialog.c
@ -3608,10 +3608,54 @@ const SeatDialogPromptDescriptions *gtk_seat_prompt_descriptions(Seat *seat)
|
||||
.hk_connect_once_action = "press \"Connect Once\"",
|
||||
.hk_cancel_action = "press \"Cancel\"",
|
||||
.hk_cancel_action_Participle = "Pressing \"Cancel\"",
|
||||
.weak_accept_action = "press \"Yes\"",
|
||||
.weak_cancel_action = "press \"No\"",
|
||||
};
|
||||
return &descs;
|
||||
}
|
||||
|
||||
/*
|
||||
* Format a SeatDialogText into a strbuf, also adjusting the box width
|
||||
* to cope with displayed text. Returns the dialog box title.
|
||||
*/
|
||||
static const char *gtk_format_seatdialogtext(
|
||||
SeatDialogText *text, strbuf *dlg_text, int *width)
|
||||
{
|
||||
const char *dlg_title = NULL;
|
||||
|
||||
for (SeatDialogTextItem *item = text->items,
|
||||
*end = item + text->nitems; item < end; item++) {
|
||||
switch (item->type) {
|
||||
case SDT_PARA:
|
||||
put_fmt(dlg_text, "%s\n\n", item->text);
|
||||
break;
|
||||
case SDT_DISPLAY: {
|
||||
put_fmt(dlg_text, "%s\n\n", item->text);
|
||||
int thiswidth = string_width(item->text);
|
||||
if (*width < thiswidth)
|
||||
*width = thiswidth;
|
||||
break;
|
||||
}
|
||||
case SDT_SCARY_HEADING:
|
||||
/* Can't change font size or weight in this context */
|
||||
put_fmt(dlg_text, "%s\n\n", item->text);
|
||||
break;
|
||||
case SDT_TITLE:
|
||||
dlg_title = item->text;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Trim trailing newlines.
|
||||
*/
|
||||
while (strbuf_chomp(dlg_text, '\n'));
|
||||
|
||||
return dlg_title;
|
||||
}
|
||||
|
||||
SeatPromptResult gtk_seat_confirm_ssh_host_key(
|
||||
Seat *seat, const char *host, int port, const char *keytype,
|
||||
char *keystr, SeatDialogText *text, HelpCtx helpctx,
|
||||
@ -3626,35 +3670,9 @@ SeatPromptResult gtk_seat_confirm_ssh_host_key(
|
||||
button_array_hostkey, lenof(button_array_hostkey),
|
||||
};
|
||||
|
||||
const char *dlg_title = NULL;
|
||||
strbuf *dlg_text = strbuf_new();
|
||||
int width = string_width("default dialog width determination string");
|
||||
|
||||
for (SeatDialogTextItem *item = text->items,
|
||||
*end = item + text->nitems; item < end; item++) {
|
||||
switch (item->type) {
|
||||
case SDT_PARA:
|
||||
put_fmt(dlg_text, "%s\n\n", item->text);
|
||||
break;
|
||||
case SDT_DISPLAY: {
|
||||
put_fmt(dlg_text, "%s\n\n", item->text);
|
||||
int thiswidth = string_width(item->text);
|
||||
if (width < thiswidth)
|
||||
width = thiswidth;
|
||||
break;
|
||||
}
|
||||
case SDT_SCARY_HEADING:
|
||||
/* Can't change font size or weight in this context */
|
||||
put_fmt(dlg_text, "%s\n\n", item->text);
|
||||
break;
|
||||
case SDT_TITLE:
|
||||
dlg_title = item->text;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (strbuf_chomp(dlg_text, '\n'));
|
||||
strbuf *dlg_text = strbuf_new();
|
||||
const char *dlg_title = gtk_format_seatdialogtext(text, dlg_text, &width);
|
||||
|
||||
GtkWidget *mainwin, *msgbox;
|
||||
|
||||
@ -3751,19 +3769,16 @@ static void simple_prompt_result_spr_callback(void *vctx, int result)
|
||||
* below the configured 'warn' threshold).
|
||||
*/
|
||||
SeatPromptResult gtk_seat_confirm_weak_crypto_primitive(
|
||||
Seat *seat, const char *algtype, const char *algname,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{
|
||||
static const char msg[] =
|
||||
"The first %s supported by the server is "
|
||||
"%s, which is below the configured warning threshold.\n"
|
||||
"Continue with connection?";
|
||||
|
||||
char *text;
|
||||
struct simple_prompt_result_spr_ctx *result_ctx;
|
||||
GtkWidget *mainwin, *msgbox;
|
||||
|
||||
text = dupprintf(msg, algtype, algname);
|
||||
int width = string_width("Reasonably long line of text "
|
||||
"as a width template");
|
||||
strbuf *dlg_text = strbuf_new();
|
||||
const char *dlg_title = gtk_format_seatdialogtext(text, dlg_text, &width);
|
||||
|
||||
result_ctx = snew(struct simple_prompt_result_spr_ctx);
|
||||
result_ctx->callback = callback;
|
||||
@ -3773,33 +3788,26 @@ SeatPromptResult gtk_seat_confirm_weak_crypto_primitive(
|
||||
|
||||
mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
|
||||
msgbox = create_message_box(
|
||||
mainwin, "PuTTY Security Alert", text,
|
||||
string_width("Reasonably long line of text as a width template"),
|
||||
false, &buttons_yn, simple_prompt_result_spr_callback, result_ctx);
|
||||
mainwin, dlg_title, dlg_text->s, width, false,
|
||||
&buttons_yn, simple_prompt_result_spr_callback, result_ctx);
|
||||
register_dialog(seat, result_ctx->dialog_slot, msgbox);
|
||||
|
||||
sfree(text);
|
||||
strbuf_free(dlg_text);
|
||||
|
||||
return SPR_INCOMPLETE;
|
||||
}
|
||||
|
||||
SeatPromptResult gtk_seat_confirm_weak_cached_hostkey(
|
||||
Seat *seat, const char *algname, const char *betteralgs,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{
|
||||
static const char msg[] =
|
||||
"The first host key type we have stored for this server\n"
|
||||
"is %s, which is below the configured warning threshold.\n"
|
||||
"The server also provides the following types of host key\n"
|
||||
"above the threshold, which we do not have stored:\n"
|
||||
"%s\n"
|
||||
"Continue with connection?";
|
||||
|
||||
char *text;
|
||||
struct simple_prompt_result_spr_ctx *result_ctx;
|
||||
GtkWidget *mainwin, *msgbox;
|
||||
|
||||
text = dupprintf(msg, algname, betteralgs);
|
||||
int width = string_width("is ecdsa-nistp521, which is below the configured"
|
||||
" warning threshold.");
|
||||
strbuf *dlg_text = strbuf_new();
|
||||
const char *dlg_title = gtk_format_seatdialogtext(text, dlg_text, &width);
|
||||
|
||||
result_ctx = snew(struct simple_prompt_result_spr_ctx);
|
||||
result_ctx->callback = callback;
|
||||
@ -3809,13 +3817,11 @@ SeatPromptResult gtk_seat_confirm_weak_cached_hostkey(
|
||||
|
||||
mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
|
||||
msgbox = create_message_box(
|
||||
mainwin, "PuTTY Security Alert", text,
|
||||
string_width("is ecdsa-nistp521, which is below the configured"
|
||||
" warning threshold."),
|
||||
false, &buttons_yn, simple_prompt_result_spr_callback, result_ctx);
|
||||
mainwin, dlg_title, dlg_text->s, width, false,
|
||||
&buttons_yn, simple_prompt_result_spr_callback, result_ctx);
|
||||
register_dialog(seat, result_ctx->dialog_slot, msgbox);
|
||||
|
||||
sfree(text);
|
||||
strbuf_free(dlg_text);
|
||||
|
||||
return SPR_INCOMPLETE;
|
||||
}
|
||||
|
@ -229,10 +229,10 @@ SeatPromptResult gtk_seat_confirm_ssh_host_key(
|
||||
char *keystr, SeatDialogText *text, HelpCtx helpctx,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
|
||||
SeatPromptResult gtk_seat_confirm_weak_crypto_primitive(
|
||||
Seat *seat, const char *algtype, const char *algname,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
|
||||
SeatPromptResult gtk_seat_confirm_weak_cached_hostkey(
|
||||
Seat *seat, const char *algname, const char *betteralgs,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
|
||||
const SeatDialogPromptDescriptions *gtk_seat_prompt_descriptions(Seat *seat);
|
||||
#ifdef MAY_REFER_TO_GTK_IN_HEADERS
|
||||
|
@ -255,7 +255,7 @@ static SeatPromptResult tempseat_confirm_ssh_host_key(
|
||||
}
|
||||
|
||||
static SeatPromptResult tempseat_confirm_weak_crypto_primitive(
|
||||
Seat *seat, const char *algtype, const char *algname,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{
|
||||
unreachable("confirm_weak_crypto_primitive "
|
||||
@ -263,7 +263,7 @@ static SeatPromptResult tempseat_confirm_weak_crypto_primitive(
|
||||
}
|
||||
|
||||
static SeatPromptResult tempseat_confirm_weak_cached_hostkey(
|
||||
Seat *seat, const char *algname, const char *betteralgs,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{
|
||||
unreachable("confirm_weak_cached_hostkey "
|
||||
|
@ -337,14 +337,16 @@ static ResponseType parse_and_free_response(char *line)
|
||||
return toret;
|
||||
}
|
||||
|
||||
SeatPromptResult console_confirm_ssh_host_key(
|
||||
Seat *seat, const char *host, int port, const char *keytype,
|
||||
char *keystr, SeatDialogText *text, HelpCtx helpctx,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
/*
|
||||
* Helper function to print the message from a SeatDialogText. Returns
|
||||
* the final prompt to print on the input line, or NULL if a
|
||||
* batch-mode abort is needed. In the latter case it will have printed
|
||||
* the abort text already.
|
||||
*/
|
||||
static const char *console_print_seatdialogtext(
|
||||
ConsoleIO *conio, SeatDialogText *text)
|
||||
{
|
||||
ConsoleIO *conio = conio_setup(false);
|
||||
const char *prompt = NULL;
|
||||
SeatPromptResult result;
|
||||
|
||||
for (SeatDialogTextItem *item = text->items,
|
||||
*end = item+text->nitems; item < end; item++) {
|
||||
@ -364,9 +366,7 @@ SeatPromptResult console_confirm_ssh_host_key(
|
||||
case SDT_BATCH_ABORT:
|
||||
if (console_batch_mode) {
|
||||
put_fmt(conio, "%s\n", item->text);
|
||||
result = SPR_SW_ABORT(
|
||||
"Cannot confirm a host key in batch mode");
|
||||
goto out;
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
case SDT_PROMPT:
|
||||
@ -377,6 +377,20 @@ SeatPromptResult console_confirm_ssh_host_key(
|
||||
}
|
||||
}
|
||||
assert(prompt); /* something in the SeatDialogText should have set this */
|
||||
return prompt;
|
||||
}
|
||||
|
||||
SeatPromptResult console_confirm_ssh_host_key(
|
||||
Seat *seat, const char *host, int port, const char *keytype,
|
||||
char *keystr, SeatDialogText *text, HelpCtx helpctx,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{
|
||||
ConsoleIO *conio = conio_setup(false);
|
||||
SeatPromptResult result;
|
||||
|
||||
const char *prompt = console_print_seatdialogtext(conio, text);
|
||||
if (!prompt)
|
||||
return SPR_SW_ABORT("Cannot confirm a host key in batch mode");
|
||||
|
||||
ResponseType response;
|
||||
|
||||
@ -416,28 +430,24 @@ SeatPromptResult console_confirm_ssh_host_key(
|
||||
put_dataz(conio, console_abandoned_msg);
|
||||
result = SPR_USER_ABORT;
|
||||
}
|
||||
out:
|
||||
|
||||
conio_free(conio);
|
||||
return result;
|
||||
}
|
||||
|
||||
SeatPromptResult console_confirm_weak_crypto_primitive(
|
||||
Seat *seat, const char *algtype, const char *algname,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{
|
||||
ConsoleIO *conio = conio_setup(false);
|
||||
SeatPromptResult result;
|
||||
|
||||
put_fmt(conio, weakcrypto_msg_common_fmt, algtype, algname);
|
||||
const char *prompt = console_print_seatdialogtext(conio, text);
|
||||
if (!prompt)
|
||||
return SPR_SW_ABORT("Cannot confirm a weak crypto primitive "
|
||||
"in batch mode");
|
||||
|
||||
if (console_batch_mode) {
|
||||
put_dataz(conio, console_abandoned_msg);
|
||||
result = SPR_SW_ABORT("Cannot confirm a weak crypto primitive "
|
||||
"in batch mode");
|
||||
goto out;
|
||||
}
|
||||
|
||||
put_dataz(conio, console_continue_prompt);
|
||||
put_fmt(conio, "%s (y/n)", prompt);
|
||||
|
||||
ResponseType response = parse_and_free_response(
|
||||
console_read_line(conio, true));
|
||||
@ -448,28 +458,24 @@ SeatPromptResult console_confirm_weak_crypto_primitive(
|
||||
put_dataz(conio, console_abandoned_msg);
|
||||
result = SPR_USER_ABORT;
|
||||
}
|
||||
out:
|
||||
|
||||
conio_free(conio);
|
||||
return result;
|
||||
}
|
||||
|
||||
SeatPromptResult console_confirm_weak_cached_hostkey(
|
||||
Seat *seat, const char *algname, const char *betteralgs,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{
|
||||
ConsoleIO *conio = conio_setup(false);
|
||||
SeatPromptResult result;
|
||||
|
||||
put_fmt(conio, weakhk_msg_common_fmt, algname, betteralgs);
|
||||
const char *prompt = console_print_seatdialogtext(conio, text);
|
||||
if (!prompt)
|
||||
return SPR_SW_ABORT("Cannot confirm a weak cached host key "
|
||||
"in batch mode");
|
||||
|
||||
if (console_batch_mode) {
|
||||
put_dataz(conio, console_abandoned_msg);
|
||||
result = SPR_SW_ABORT("Cannot confirm a weak cached host key "
|
||||
"in batch mode");
|
||||
goto out;
|
||||
}
|
||||
|
||||
put_dataz(conio, console_continue_prompt);
|
||||
put_fmt(conio, "%s (y/n) ", prompt);
|
||||
|
||||
ResponseType response = parse_and_free_response(
|
||||
console_read_line(conio, true));
|
||||
@ -480,7 +486,7 @@ SeatPromptResult console_confirm_weak_cached_hostkey(
|
||||
put_dataz(conio, console_abandoned_msg);
|
||||
result = SPR_USER_ABORT;
|
||||
}
|
||||
out:
|
||||
|
||||
conio_free(conio);
|
||||
return result;
|
||||
}
|
||||
|
112
windows/dialog.c
112
windows/dialog.c
@ -949,6 +949,39 @@ static INT_PTR HostKeyMoreInfoProc(HWND hwnd, UINT msg, WPARAM wParam,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *process_seatdialogtext(
|
||||
strbuf *dlg_text, const char **scary_heading, SeatDialogText *text)
|
||||
{
|
||||
const char *dlg_title = "";
|
||||
|
||||
for (SeatDialogTextItem *item = text->items,
|
||||
*end = item + text->nitems; item < end; item++) {
|
||||
switch (item->type) {
|
||||
case SDT_PARA:
|
||||
put_fmt(dlg_text, "%s\r\n\r\n", item->text);
|
||||
break;
|
||||
case SDT_DISPLAY:
|
||||
put_fmt(dlg_text, "%s\r\n\r\n", item->text);
|
||||
break;
|
||||
case SDT_SCARY_HEADING:
|
||||
assert(scary_heading != NULL && "only expect a scary heading if "
|
||||
"the dialog has somewhere to put it");
|
||||
*scary_heading = item->text;
|
||||
break;
|
||||
case SDT_TITLE:
|
||||
dlg_title = item->text;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Trim any trailing newlines */
|
||||
while (strbuf_chomp(dlg_text, '\r') || strbuf_chomp(dlg_text, '\n'));
|
||||
|
||||
return dlg_title;
|
||||
}
|
||||
|
||||
static INT_PTR HostKeyDialogProc(HWND hwnd, UINT msg,
|
||||
WPARAM wParam, LPARAM lParam, void *vctx)
|
||||
{
|
||||
@ -957,32 +990,15 @@ static INT_PTR HostKeyDialogProc(HWND hwnd, UINT msg,
|
||||
switch (msg) {
|
||||
case WM_INITDIALOG: {
|
||||
strbuf *dlg_text = strbuf_new();
|
||||
const char *dlg_title = "";
|
||||
ctx->has_title = false;
|
||||
LPCTSTR iconid = IDI_QUESTION;
|
||||
const char *scary_heading = NULL;
|
||||
const char *dlg_title = process_seatdialogtext(
|
||||
dlg_text, &scary_heading, ctx->text);
|
||||
|
||||
for (SeatDialogTextItem *item = ctx->text->items,
|
||||
*end = item + ctx->text->nitems; item < end; item++) {
|
||||
switch (item->type) {
|
||||
case SDT_PARA:
|
||||
put_fmt(dlg_text, "%s\r\n\r\n", item->text);
|
||||
break;
|
||||
case SDT_DISPLAY:
|
||||
put_fmt(dlg_text, "%s\r\n\r\n", item->text);
|
||||
break;
|
||||
case SDT_SCARY_HEADING:
|
||||
SetDlgItemText(hwnd, IDC_HK_TITLE, item->text);
|
||||
iconid = IDI_WARNING;
|
||||
ctx->has_title = true;
|
||||
break;
|
||||
case SDT_TITLE:
|
||||
dlg_title = item->text;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
LPCTSTR iconid = IDI_QUESTION;
|
||||
if (scary_heading) {
|
||||
SetDlgItemText(hwnd, IDC_HK_TITLE, scary_heading);
|
||||
iconid = IDI_WARNING;
|
||||
}
|
||||
while (strbuf_chomp(dlg_text, '\r') || strbuf_chomp(dlg_text, '\n'));
|
||||
|
||||
SetDlgItemText(hwnd, IDC_HK_TEXT, dlg_text->s);
|
||||
MakeDlgItemBorderless(hwnd, IDC_HK_TEXT);
|
||||
@ -1121,6 +1137,8 @@ const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat)
|
||||
.hk_connect_once_action = "press \"Connect Once\"",
|
||||
.hk_cancel_action = "press \"Cancel\"",
|
||||
.hk_cancel_action_Participle = "Pressing \"Cancel\"",
|
||||
.weak_accept_action = "press \"Yes\"",
|
||||
.weak_cancel_action = "press \"No\"",
|
||||
};
|
||||
return &descs;
|
||||
}
|
||||
@ -1155,25 +1173,17 @@ SeatPromptResult win_seat_confirm_ssh_host_key(
|
||||
* below the configured 'warn' threshold).
|
||||
*/
|
||||
SeatPromptResult win_seat_confirm_weak_crypto_primitive(
|
||||
Seat *seat, const char *algtype, const char *algname,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{
|
||||
static const char mbtitle[] = "%s Security Alert";
|
||||
static const char msg[] =
|
||||
"The first %s supported by the server\n"
|
||||
"is %s, which is below the configured\n"
|
||||
"warning threshold.\n"
|
||||
"Do you want to continue with this connection?\n";
|
||||
char *message, *title;
|
||||
int mbret;
|
||||
strbuf *dlg_text = strbuf_new();
|
||||
const char *dlg_title = process_seatdialogtext(dlg_text, NULL, text);
|
||||
|
||||
message = dupprintf(msg, algtype, algname);
|
||||
title = dupprintf(mbtitle, appname);
|
||||
mbret = MessageBox(NULL, message, title,
|
||||
MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
|
||||
int mbret = MessageBox(NULL, dlg_text->s, dlg_title,
|
||||
MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
|
||||
socket_reselect_all();
|
||||
sfree(message);
|
||||
sfree(title);
|
||||
strbuf_free(dlg_text);
|
||||
|
||||
if (mbret == IDYES)
|
||||
return SPR_OK;
|
||||
else
|
||||
@ -1181,27 +1191,17 @@ SeatPromptResult win_seat_confirm_weak_crypto_primitive(
|
||||
}
|
||||
|
||||
SeatPromptResult win_seat_confirm_weak_cached_hostkey(
|
||||
Seat *seat, const char *algname, const char *betteralgs,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
||||
{
|
||||
static const char mbtitle[] = "%s Security Alert";
|
||||
static const char msg[] =
|
||||
"The first host key type we have stored for this server\n"
|
||||
"is %s, which is below the configured warning threshold.\n"
|
||||
"The server also provides the following types of host key\n"
|
||||
"above the threshold, which we do not have stored:\n"
|
||||
"%s\n"
|
||||
"Do you want to continue with this connection?\n";
|
||||
char *message, *title;
|
||||
int mbret;
|
||||
strbuf *dlg_text = strbuf_new();
|
||||
const char *dlg_title = process_seatdialogtext(dlg_text, NULL, text);
|
||||
|
||||
message = dupprintf(msg, algname, betteralgs);
|
||||
title = dupprintf(mbtitle, appname);
|
||||
mbret = MessageBox(NULL, message, title,
|
||||
MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
|
||||
int mbret = MessageBox(NULL, dlg_text->s, dlg_title,
|
||||
MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
|
||||
socket_reselect_all();
|
||||
sfree(message);
|
||||
sfree(title);
|
||||
strbuf_free(dlg_text);
|
||||
|
||||
if (mbret == IDYES)
|
||||
return SPR_OK;
|
||||
else
|
||||
|
@ -254,10 +254,10 @@ SeatPromptResult win_seat_confirm_ssh_host_key(
|
||||
char *keystr, SeatDialogText *text, HelpCtx helpctx,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
|
||||
SeatPromptResult win_seat_confirm_weak_crypto_primitive(
|
||||
Seat *seat, const char *algtype, const char *algname,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
|
||||
SeatPromptResult win_seat_confirm_weak_cached_hostkey(
|
||||
Seat *seat, const char *algname, const char *betteralgs,
|
||||
Seat *seat, SeatDialogText *text,
|
||||
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
|
||||
const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user