1
0
mirror of https://github.com/winscp/winscp.git synced 2026-10-24 02:28:55 +03:00

Updating to PuTTY 0.80 – mostly Issue 2246 – Mitigations for SSH protocol "Terrapin" vulnerability

https://winscp.net/tracker/2246

Source commit: f7b34a47547af9b0b4bd32fb66d6d278c8b6b342
This commit is contained in:
Martin Prikryl
2023-12-20 12:18:25 +00:00
parent a65773c31c
commit a6adfa0ad3
11 changed files with 104 additions and 42 deletions

View File

@@ -291,18 +291,21 @@ bool have_ssh_host_key(Seat * seat, const char * hostname, int port,
return SecureShell->HaveHostKey(hostname, port, keytype) ? 1 : 0;
}
//---------------------------------------------------------------------------
SeatPromptResult confirm_weak_crypto_primitive(Seat * seat, const char * algtype, const char * algname,
void (*/*callback*/)(void * ctx, SeatPromptResult result), void * /*ctx*/)
SeatPromptResult confirm_weak_crypto_primitive(
Seat * seat, SeatDialogText *,
void (*DebugUsedArg(callback))(void * ctx, SeatPromptResult result), void * DebugUsedArg(ctx),
const char * algtype, const char *algname, int wcr)
{
TSecureShell * SecureShell = static_cast<ScpSeat *>(seat)->SecureShell;
SecureShell->AskAlg(algtype, algname);
SecureShell->AskAlg(algtype, algname, wcr);
// We should return 0 when alg was not confirmed, we throw exception instead.
return SPR_OK;
}
//---------------------------------------------------------------------------
SeatPromptResult confirm_weak_cached_hostkey(Seat *, const char * /*algname*/, const char * /*betteralgs*/,
void (*/*callback*/)(void *ctx, SeatPromptResult result), void * /*ctx*/)
SeatPromptResult confirm_weak_cached_hostkey(
Seat *, SeatDialogText *,
void (*DebugUsedArg(callback))(void * ctx, SeatPromptResult result), void * DebugUsedArg(ctx))
{
return SPR_OK;
}
@@ -470,11 +473,12 @@ int reg_override_winscp()
return (PuttyRegistryMode != prmPass);
}
//---------------------------------------------------------------------------
HKEY open_regkey_fn_winscp(bool Create, HKEY Key, const char * Path, ...)
HKEY open_regkey_fn_winscp(bool Create, bool Write, HKEY Key, const char * Path, ...)
{
HKEY Result;
if (PuttyRegistryMode == prmCollect)
{
DebugAssert(Write && Create);
Result = reinterpret_cast<HKEY>(1);
}
else if (PuttyRegistryMode == prmFail)
@@ -519,7 +523,7 @@ HKEY open_regkey_fn_winscp(bool Create, HKEY Key, const char * Path, ...)
DebugAssert(RegKey == L"SshHostKeys");
DebugAssert(PuttyStorage != NULL);
DebugAssert(PuttyStorage->AccessMode == (Create ? smReadWrite : smRead));
DebugAssert(PuttyStorage->AccessMode == (Write ? smReadWrite : smRead));
if (PuttyStorage->OpenSubKey(RegKey, Create))
{
Result = reinterpret_cast<HKEY>(PuttyStorage);

View File

@@ -2841,7 +2841,7 @@ bool __fastcall TSecureShell::HaveHostKey(UnicodeString Host, int Port, const Un
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TSecureShell::AskAlg(UnicodeString AlgType, UnicodeString AlgName)
void TSecureShell::AskAlg(const UnicodeString & AAlgType, const UnicodeString & AlgName, int WeakCryptoReason)
{
// beware of changing order
static const TPuttyTranslation AlgTranslation[] = {
@@ -2852,9 +2852,33 @@ void __fastcall TSecureShell::AskAlg(UnicodeString AlgType, UnicodeString AlgNam
{ L"hostkey type", KEYKEY_TYPE },
};
UnicodeString AlgType = AAlgType;
TranslatePuttyMessage(AlgTranslation, LENOF(AlgTranslation), AlgType);
UnicodeString Msg = FMTLOAD(ALG_BELOW_TRESHOLD, (AlgType, AlgName));
UnicodeString Msg;
UnicodeString NewLine = UnicodeString(sLineBreak);
switch (WeakCryptoReason)
{
default:
DebugFail();
case 0:
Msg = FMTLOAD(ALG_BELOW_TRESHOLD2, (AlgType, AlgName));
break;
case 1:
case 2:
Msg = FMTLOAD(CIPHER_TERRAPIN, (AlgType, AlgName));
if (WeakCryptoReason == 2)
{
Msg += NewLine + NewLine + FMTLOAD(CIPHER_TERRAPIN_AVOID, (AlgName));
}
break;
}
Msg =
MainInstructions(LoadStr(WEAK_ALG_TITLE)) + NewLine + NewLine +
Msg + NewLine + NewLine +
LoadStr(WEAK_ALG_RISK_CONFIRM);
if (FUI->QueryUser(Msg, NULL, qaYes | qaNo, NULL, qtWarning) == qaNo)
{

View File

@@ -175,7 +175,7 @@ public:
const UnicodeString & FingerprintSHA256, const UnicodeString & FingerprintMD5,
bool IsCertificate, int CACount, bool AlreadyVerified);
bool __fastcall HaveHostKey(UnicodeString Host, int Port, const UnicodeString KeyType);
void __fastcall AskAlg(UnicodeString AlgType, UnicodeString AlgName);
void AskAlg(const UnicodeString & AlgType, const UnicodeString & AlgName, int WeakCryptoReason);
void __fastcall DisplayBanner(const UnicodeString & Banner);
void __fastcall PuttyLogEvent(const char * Str);
UnicodeString __fastcall ConvertFromPutty(const char * Str, int Length);

12
source/putty/putty.h vendored
View File

@@ -1295,7 +1295,8 @@ struct SeatVtable {
*/
SeatPromptResult (*confirm_weak_crypto_primitive)(
Seat *seat, SeatDialogText *text,
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
void (*callback)(void *ctx, SeatPromptResult result), void *ctx,
const char *algtype, const char *algname, int wcr);
/*
* Variant form of confirm_weak_crypto_primitive, which prints a
@@ -1447,9 +1448,11 @@ static inline SeatPromptResult seat_confirm_ssh_host_key(
fingerprints, is_certificate, ca_count, already_verified); } // WINSCP
static inline SeatPromptResult seat_confirm_weak_crypto_primitive(
InteractionReadySeat iseat, SeatDialogText *text,
void (*cb)(void *ctx, SeatPromptResult result), void *ctx)
void (*cb)(void *ctx, SeatPromptResult result), void *ctx,
const char *algtype, const char *algname, int wcr) // WINSCP
{ return iseat.seat->vt->confirm_weak_crypto_primitive(
iseat.seat, text, cb, ctx); }
iseat.seat, text, cb, ctx,
algtype, algname, wcr); } // WINSCP
static inline SeatPromptResult seat_confirm_weak_cached_hostkey(
InteractionReadySeat iseat, SeatDialogText *text,
void (*cb)(void *ctx, SeatPromptResult result), void *ctx)
@@ -1542,7 +1545,8 @@ SeatPromptResult nullseat_confirm_ssh_host_key(
char **fingerprints, bool is_certificate, int ca_count, bool already_verified); // WINSCP
SeatPromptResult nullseat_confirm_weak_crypto_primitive(
Seat *seat, SeatDialogText *text,
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
void (*callback)(void *ctx, SeatPromptResult result), void *ctx,
const char *algtype, const char *algname, int wcr); // WINSCP
SeatPromptResult nullseat_confirm_weak_cached_hostkey(
Seat *seat, SeatDialogText *text,
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);

View File

@@ -1188,10 +1188,13 @@ SeatPromptResult confirm_weak_crypto_primitive(
seat_dialog_text_append(text, SDT_PROMPT, "Continue with connection?");
{ // WINSCP
SeatPromptResult toret = seat_confirm_weak_crypto_primitive(
iseat, text, callback, ctx);
iseat, text, callback, ctx,
algtype, algname, wcr); // WINSCP
seat_dialog_text_free(text);
return toret;
} // WINSCP
}
SeatPromptResult confirm_weak_cached_hostkey(
@@ -1214,8 +1217,11 @@ SeatPromptResult confirm_weak_cached_hostkey(
"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++)
{ // WINSCP
const char **p; // WINSCP
for (p = betteralgs; *p; p++)
seat_dialog_text_append(text, SDT_DISPLAY, "%s", *p);
} // WINSCP
/* In batch mode, we print the above information and then this
* abort message, and stop. */
@@ -1228,10 +1234,12 @@ SeatPromptResult confirm_weak_cached_hostkey(
seat_dialog_text_append(text, SDT_PROMPT, "Continue with connection?");
{ // WINSCP
SeatPromptResult toret = seat_confirm_weak_cached_hostkey(
iseat, text, callback, ctx);
seat_dialog_text_free(text);
return toret;
} // WINSCP
}
/* ----------------------------------------------------------------------

View File

@@ -1023,7 +1023,8 @@ struct server_hostkeys {
static bool kexinit_keyword_found(ptrlen list, ptrlen keyword)
{
for (ptrlen word; get_commasep_word(&list, &word) ;)
ptrlen word; // WINSCP
for (; get_commasep_word(&list, &word) ;)
if (ptrlen_eq_ptrlen(word, keyword))
return true;
return false;
@@ -1079,7 +1080,7 @@ static ScanKexinitsResult ssh2_scan_kexinits(
slists[i] = get_string(server);
if (get_err(client) || get_err(server)) {
ScanKexinitsResult skr = {
.success = false, .error = SKR_INCOMPLETE,
/*.success =*/ false, /*.error =*/ SKR_INCOMPLETE,
};
return skr;
}
@@ -1129,10 +1130,9 @@ static ScanKexinitsResult ssh2_scan_kexinits(
* produce a reasonably useful message instead of an
* assertion failure.
*/
ScanKexinitsResult skr = {
.success = false, .error = SKR_UNKNOWN_ID,
.kind = kexlist_descr[i], .desc = found,
};
ScanKexinitsResult skr;
skr.success = false, skr.error = SKR_UNKNOWN_ID,
skr.kind = kexlist_descr[i], skr.desc = found;
return skr;
}
@@ -1188,10 +1188,9 @@ static ScanKexinitsResult ssh2_scan_kexinits(
/*
* Otherwise, any match failure _is_ a fatal error.
*/
ScanKexinitsResult skr = {
.success = false, .error = SKR_UNKNOWN_ID,
.kind = kexlist_descr[i], .desc = slists[i],
};
ScanKexinitsResult skr;
skr.success = false, skr.error = SKR_UNKNOWN_ID,
skr.kind = kexlist_descr[i], skr.desc = slists[i];
return skr;
}
@@ -1291,8 +1290,10 @@ static ScanKexinitsResult ssh2_scan_kexinits(
} // WINSCP
}
ScanKexinitsResult skr = { .success = true };
{ // WINSCP
ScanKexinitsResult skr = { /*.success =*/ true };
return skr;
} // WINSCP
}
static void ssh2_report_scan_kexinits_error(Ssh *ssh, ScanKexinitsResult skr)
@@ -1381,6 +1382,7 @@ static void filter_outgoing_kexinit(struct ssh2_transport_state *s)
searchword = kex_strict_c;
}
{ // WINSCP
bool add = false;
ptrlen iword; // WINSCP
for (; get_commasep_word(&ilist_copy, &iword) ;) {
@@ -1405,6 +1407,7 @@ static void filter_outgoing_kexinit(struct ssh2_transport_state *s)
if (add)
add_to_commasep_pl(out, oword);
} // WINSCP
}
put_stringpl(pktout, ptrlen_from_strbuf(out));
} // WINSCP
@@ -2697,8 +2700,10 @@ static bool try_to_avoid_terrapin(const struct ssh2_transport_state *s)
* previously above it. If not, don't do anything - we don't want
* to _promote_ it.
*/
{ // WINSCP
int ccp_pos_now = -1, ccp_pos_wanted = -1;
for (int i = 0; i < CIPHER_MAX; i++) {
int i; // WINSCP
for (i = 0; i < CIPHER_MAX; i++) {
switch (conf_get_int_int(alt_conf, CONF_ssh_cipherlist,
i)) {
case CIPHER_CHACHA20:
@@ -2722,6 +2727,7 @@ static bool try_to_avoid_terrapin(const struct ssh2_transport_state *s)
}
conf_set_int_int(alt_conf, CONF_ssh_cipherlist,
ccp_pos_now + 1, CIPHER_CHACHA20);
} // WINSCP
/*
* Make the outgoing KEXINIT we would have made using this
@@ -2730,7 +2736,7 @@ static bool try_to_avoid_terrapin(const struct ssh2_transport_state *s)
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,
/*WINSCP*/ s->ppl.seat, 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,
@@ -2742,6 +2748,7 @@ static bool try_to_avoid_terrapin(const struct ssh2_transport_state *s)
* Re-analyse the incoming KEXINIT with respect to this one, to
* see what we'd have decided on.
*/
{ // WINSCP
transport_direction cstrans, sctrans;
bool warn_kex, warn_hk, warn_cscipher, warn_sccipher;
bool can_send_ext_info = false, strict_kex = false;
@@ -2785,12 +2792,16 @@ static bool try_to_avoid_terrapin(const struct ssh2_transport_state *s)
out:
for (size_t i = 0; i < NKEXLIST; i++)
{ // WINSCP
size_t i;
for (i = 0; i < NKEXLIST; i++)
sfree(alt_kexlists[i].algs);
strbuf_free(alt_client_kexinit);
conf_free(alt_conf);
return avoidable;
} // WINSCP
} // WINSCP
}
#include "puttyexp.h"

View File

@@ -28,7 +28,8 @@ SeatPromptResult nullseat_confirm_ssh_host_key(
{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); }
SeatPromptResult nullseat_confirm_weak_crypto_primitive(
Seat *seat, SeatDialogText *text,
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
void (*callback)(void *ctx, SeatPromptResult result), void *ctx,
const char *algtype, const char *algname, int wcr) // WINSCP
{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); }
SeatPromptResult nullseat_confirm_weak_cached_hostkey(
Seat *seat, SeatDialogText *text,
@@ -61,8 +62,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 = "",
/*.weak_accept_action =*/ "",
/*.weak_cancel_action =*/ "",
};
return &descs;
}

View File

@@ -257,7 +257,8 @@ static SeatPromptResult tempseat_confirm_ssh_host_key(
static SeatPromptResult tempseat_confirm_weak_crypto_primitive(
Seat *seat, SeatDialogText *text,
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
void (*callback)(void *ctx, SeatPromptResult result), void *ctx,
const char *algtype, const char *algname, int wcr) // WINSCP
{
unreachable("confirm_weak_crypto_primitive "
"should never be called on TempSeat");

View File

@@ -244,6 +244,7 @@ void launch_help(HWND hwnd, const char *topic);
void quit_help(HWND hwnd);
int has_embedded_chm(void); /* 1 = yes, 0 = no, -1 = N/A */
#ifndef WINSCP
/*
* GUI seat methods in dialog.c, so that the vtable definition in
* window.c can refer to them.
@@ -251,8 +252,7 @@ int has_embedded_chm(void); /* 1 = yes, 0 = no, -1 = N/A */
SeatPromptResult win_seat_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 **fingerprints, bool is_certificate, int ca_count, bool already_verified); // WINSCP
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
SeatPromptResult win_seat_confirm_weak_crypto_primitive(
Seat *seat, SeatDialogText *text,
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
@@ -260,6 +260,7 @@ SeatPromptResult win_seat_confirm_weak_cached_hostkey(
Seat *seat, SeatDialogText *text,
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat);
#endif
/*
* Windows-specific clipboard helper function shared with dialog.c,
@@ -753,13 +754,13 @@ char *get_jumplist_registry_entries(void);
/* In utils */
int reg_override_winscp(void);
HKEY open_regkey_fn(bool create, bool write, HKEY base, const char *path, ...);
HKEY open_regkey_fn_winscp(bool create, HKEY base, const char *path, ...);
HKEY open_regkey_fn_winscp(bool create, bool write, HKEY base, const char *path, ...);
#define open_regkey_ro(base, ...) \
open_regkey_fn(false, false, base, __VA_ARGS__, (const char *)NULL)
reg_override_winscp() ? open_regkey_fn_winscp(false, false, base, __VA_ARGS__, (const char *)NULL) : open_regkey_fn(false, false, base, __VA_ARGS__, (const char *)NULL)
#define open_regkey_rw(base, ...) \
open_regkey_fn(false, true, base, __VA_ARGS__, (const char *)NULL)
DebugFail();
#define create_regkey(base, ...) \
open_regkey_fn(true, true, base, __VA_ARGS__, (const char *)NULL)
reg_override_winscp() ? open_regkey_fn_winscp(true, true, base, __VA_ARGS__, (const char *)NULL) : open_regkey_fn(true, true, base, __VA_ARGS__, (const char *)NULL)
void close_regkey(HKEY key);
void close_regkey_winscp(HKEY key);
void del_regkey(HKEY key, const char *name);

View File

@@ -294,7 +294,6 @@
#define PROMPT_KEY_PASSPHRASE 303
#define FILE_OVERWRITE 304
#define DIRECTORY_OVERWRITE 305
#define ALG_BELOW_TRESHOLD 306
#define CIPHER_TYPE_BOTH2 307
#define CIPHER_TYPE_CS2 308
#define CIPHER_TYPE_SC2 309
@@ -372,6 +371,11 @@
#define HOSTKEY_CERTIFIED_ANOTHER 388
#define HOSTKEY_CERTIFIED_DOESNT_MATCH 389
#define HOSTKEY_CERTIFIED_TRUST 390
#define WEAK_ALG_TITLE 391
#define WEAK_ALG_RISK_CONFIRM 392
#define ALG_BELOW_TRESHOLD2 393
#define CIPHER_TERRAPIN 394
#define CIPHER_TERRAPIN_AVOID 395
#define CORE_INFORMATION_STRINGS 400
#define YES_STR 401

View File

@@ -270,7 +270,6 @@ BEGIN
PROMPT_KEY_PASSPHRASE, "&Passphrase for key '%s':"
FILE_OVERWRITE, "File '%s' already exists. Overwrite?"
DIRECTORY_OVERWRITE, "Directory '%s' already exists. Overwrite?"
ALG_BELOW_TRESHOLD, "The first %s supported by the server is %s, which is below the configured warning threshold.\n\nDo you want to continue with this connection?"
CIPHER_TYPE_BOTH2, "cipher"
CIPHER_TYPE_CS2, "client-to-server cipher"
CIPHER_TYPE_SC2, "server-to-client cipher"
@@ -345,6 +344,11 @@ BEGIN
HOSTKEY_CERTIFIED_ANOTHER, "another certification authority is operating in this realm"
HOSTKEY_CERTIFIED_DOESNT_MATCH, "which does not match the certified key WinSCP had previously cached for this server."
HOSTKEY_CERTIFIED_TRUST, "(Storing this certified key in the cache will NOT cause its certification authority to be trusted for any other key or host.)"
WEAK_ALG_TITLE, "Security Alert"
WEAK_ALG_RISK_CONFIRM, "Do you want to accept the risk and continue with this session?"
ALG_BELOW_TRESHOLD2, "The first %s supported by the server is %s, which is below the configured warning threshold."
CIPHER_TERRAPIN, "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.\n\nUpgrading, patching, or reconfiguring this SSH server is the best way to avoid this vulnerability, if possible."
CIPHER_TERRAPIN_AVOID, "You can also avoid this vulnerability by abandoning this session, moving %s to below the 'warn below here' line in WinSCP's SSH cipher configuration (so that an algorithm without the vulnerability will be selected), and starting a new session."
CORE_INFORMATION_STRINGS, "CORE_INFORMATION"
YES_STR, "Yes"