diff --git a/config.c b/config.c index 8cdeee24..04c807a1 100644 --- a/config.c +++ b/config.c @@ -3173,6 +3173,11 @@ void setup_config_box(struct controlbox *b, bool midsession, s = ctrl_getset(b, "Connection/SSH/More bugs", "main", "Detection of known bugs in SSH servers"); + ctrl_droplist(s, "Rejects rsa-sha2-*-cert*@openssh.com in userauth", + 'j', 20, + HELPCTX(ssh_bugs_rsa_sha2_cert_userauth), + sshbug_handler, + I(CONF_sshbug_rsa_sha2_cert_userauth)); ctrl_droplist(s, "Requires padding on SSH-2 RSA signatures", 'p', 20, HELPCTX(ssh_bugs_rsapad2), sshbug_handler, I(CONF_sshbug_rsapad2)); diff --git a/doc/config.but b/doc/config.but index 56eac575..65d3e85f 100644 --- a/doc/config.but +++ b/doc/config.but @@ -3664,6 +3664,23 @@ connection would deadlock. We don't know of any servers that do this, but if there is one, then this flag will make PuTTY unable to speak to them at all. +\S{config-ssh-bug-rsa-sha2-cert-userauth} \q{Rejects +\cw{rsa-sha2-*-cert*@openssh.com} in userauth} + +If PuTTY is trying to do SSH-2 user authentication using an RSA key, +and the server is using one of the newer SHA-2 based versions of the +SSH RSA protocol, and the user's key is also a certificate, then +earlier versions of OpenSSH (up to 7.7) disagree with later versions +about the right key algorithm string to send in the +\cw{SSH2_MSG_USERAUTH_REQUEST} packet. Modern versions send a string +that indicates both the SHA-2 nature and the certificate nature of the +key, such as \cq{rsa-sha2-512-cert-v01@openssh.com}. Earlier versions +would reject that, and insist on seeing +\cq{ssh-rsa-cert-v01@openssh.com} followed by a SHA-2 based signature. + +PuTTY should auto-detect the presence of this bug in earlier OpenSSH +and adjust to send the right string. + \S{config-ssh-bug-sig} \q{Requires padding on SSH-2 \i{RSA} \i{signatures}} Versions below 3.3 of \i{OpenSSH} require SSH-2 RSA signatures to be diff --git a/putty.h b/putty.h index e413552c..abf7c4f3 100644 --- a/putty.h +++ b/putty.h @@ -2025,6 +2025,7 @@ NORETURN void cleanup_exit(int); X(INT, NONE, sshbug_chanreq) \ X(INT, NONE, sshbug_dropstart) \ X(INT, NONE, sshbug_filter_kexinit) \ + X(INT, NONE, sshbug_rsa_sha2_cert_userauth) \ /* \ * ssh_simple means that we promise never to open any channel \ * other than the main one, which means it can safely use a very \ diff --git a/settings.c b/settings.c index 04e53a00..bd7ca609 100644 --- a/settings.c +++ b/settings.c @@ -785,6 +785,7 @@ void save_open_settings(settings_w *sesskey, Conf *conf) write_setting_i(sesskey, "BugOldGex2", 2-conf_get_int(conf, CONF_sshbug_oldgex2)); write_setting_i(sesskey, "BugWinadj", 2-conf_get_int(conf, CONF_sshbug_winadj)); write_setting_i(sesskey, "BugChanReq", 2-conf_get_int(conf, CONF_sshbug_chanreq)); + write_setting_i(sesskey, "BugRSASHA2CertUserauth", 2-conf_get_int(conf, CONF_sshbug_rsa_sha2_cert_userauth)); write_setting_i(sesskey, "BugDropStart", 2-conf_get_int(conf, CONF_sshbug_dropstart)); write_setting_i(sesskey, "BugFilterKexinit", 2-conf_get_int(conf, CONF_sshbug_filter_kexinit)); write_setting_b(sesskey, "StampUtmp", conf_get_bool(conf, CONF_stamp_utmp)); @@ -1266,6 +1267,7 @@ void load_open_settings(settings_r *sesskey, Conf *conf) i = gppi_raw(sesskey, "BugOldGex2", 0); conf_set_int(conf, CONF_sshbug_oldgex2, 2-i); i = gppi_raw(sesskey, "BugWinadj", 0); conf_set_int(conf, CONF_sshbug_winadj, 2-i); i = gppi_raw(sesskey, "BugChanReq", 0); conf_set_int(conf, CONF_sshbug_chanreq, 2-i); + i = gppi_raw(sesskey, "BugRSASHA2CertUserauth", 0); conf_set_int(conf, CONF_sshbug_rsa_sha2_cert_userauth, 2-i); i = gppi_raw(sesskey, "BugDropStart", 1); conf_set_int(conf, CONF_sshbug_dropstart, 2-i); i = gppi_raw(sesskey, "BugFilterKexinit", 1); conf_set_int(conf, CONF_sshbug_filter_kexinit, 2-i); conf_set_bool(conf, CONF_ssh_simple, false); diff --git a/ssh.h b/ssh.h index deaa3a7c..2df0e3ed 100644 --- a/ssh.h +++ b/ssh.h @@ -1882,6 +1882,7 @@ void old_keyfile_warning(void); X(BUG_SENDS_LATE_REQUEST_REPLY) \ X(BUG_SSH2_OLDGEX) \ X(BUG_REQUIRES_FILTERED_KEXINIT) \ + X(BUG_RSA_SHA2_CERT_USERAUTH) \ /* end of list */ #define TMP_DECLARE_LOG2_ENUM(thing) log2_##thing, enum { SSH_IMPL_BUG_LIST(TMP_DECLARE_LOG2_ENUM) }; diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index e68f6bde..4d4310f6 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -135,6 +135,8 @@ static void ssh2_userauth_ki_write_responses( static void ssh2_userauth_final_output(PacketProtocolLayer *ppl); static void ssh2_userauth_print_banner(struct ssh2_userauth_state *s); +static ptrlen workaround_rsa_sha2_cert_userauth( + struct ssh2_userauth_state *s, ptrlen id); static const PacketProtocolLayerVtable ssh2_userauth_vtable = { .free = ssh2_userauth_free, @@ -2381,9 +2383,19 @@ static void ssh2_userauth_add_alg_and_publickey( * bare key algorithms, or nothing useful will happen. */ const ssh_keyalg *pkalg_base = pkalg->base_alg ? pkalg->base_alg : pkalg; + + /* Construct an algorithm string that includes both the + * signature subtype (e.g. rsa-sha2-512) and the + * certificate-ness. Exception: in earlier versions of + * OpenSSH we don't want to do that, and must send just + * ssh-rsa-cert-... even when we're delivering a non-SHA-1 + * signature. */ const ssh_keyalg *output_alg = ssh_keyalg_related_alg(certalg, pkalg_base); - put_stringz(pkt, output_alg->ssh_id); + ptrlen output_id = ptrlen_from_asciz(output_alg->ssh_id); + output_id = workaround_rsa_sha2_cert_userauth(s, output_id); + + put_stringpl(pkt, output_id); } put_stringpl(pkt, ptrlen_from_strbuf(s->detached_cert_blob)); done = true; @@ -2430,11 +2442,30 @@ static void ssh2_userauth_add_alg_and_publickey( return; } - /* In all other cases, just put in what we were given. */ + /* In all other cases, basically just put in what we were given - + * except for the same bug workaround as above. */ + alg = workaround_rsa_sha2_cert_userauth(s, alg); put_stringpl(pkt, alg); put_stringpl(pkt, pkblob); } +static ptrlen workaround_rsa_sha2_cert_userauth( + struct ssh2_userauth_state *s, ptrlen id) +{ + if (!(s->ppl.remote_bugs & BUG_RSA_SHA2_CERT_USERAUTH)) + return id; + /* + * No need to try to do this in a general way based on the + * relations between ssh_keyalgs; we know there are a limited + * number of affected versions of OpenSSH, so this doesn't have to + * be futureproof against later additions to the family. + */ + if (ptrlen_eq_string(id, "rsa-sha2-256-cert-v01@openssh.com") || + ptrlen_eq_string(id, "rsa-sha2-512-cert-v01@openssh.com")) + return PTRLEN_LITERAL("ssh-rsa-cert-v01@openssh.com"); + return id; +} + /* * Helper function to add an SSH-2 signature blob to a packet. Expects * to be shown the public key blob as well as the signature blob. diff --git a/ssh/verstring.c b/ssh/verstring.c index aa4c2c20..b63810ef 100644 --- a/ssh/verstring.c +++ b/ssh/verstring.c @@ -612,6 +612,28 @@ static void ssh_detect_bugs(struct ssh_verstring_state *s) bpp_logevent("We believe remote version requires us to " "filter our KEXINIT"); } + + if (conf_get_int(s->conf, CONF_sshbug_rsa_sha2_cert_userauth) == FORCE_ON || + (conf_get_int(s->conf, CONF_sshbug_rsa_sha2_cert_userauth) == AUTO && + (wc_match("OpenSSH_7.[2-7]*", imp)))) { + /* + * These versions have the bug in which using RSA/SHA-2 + * authentication with a certified key requires the key + * algorithm to be sent as ssh-rsa-cert-... instead of + * rsa-sha2-NNN-cert-... + * + * OpenSSH 7.8 wants rsa-sha2-NNN-cert-...: + * https://github.com/openssh/openssh-portable/commit/4ba0d54794814ec0de1ec87987d0c3b89379b436 + * (also labelled "OpenBSD-Commit-ID: + * c6e9f6d45eed8962ad502d315d7eaef32c419dde") + * + * OpenSSH 7.2 was the first release supporting RSA/SHA-2 + * at all, so this bug is irrelevant to anything before that. + */ + s->remote_bugs |= BUG_RSA_SHA2_CERT_USERAUTH; + bpp_logevent("We believe remote version has SSH-2 " + "RSA/SHA-2/certificate userauth bug"); + } } const char *ssh_verstring_get_remote(BinaryPacketProtocol *bpp) diff --git a/windows/help.h b/windows/help.h index de6ec0be..cfca4e1c 100644 --- a/windows/help.h +++ b/windows/help.h @@ -167,6 +167,7 @@ typedef const char *HelpCtx; #define WINHELP_CTX_ssh_bugs_pksessid2 "config-ssh-bug-pksessid2" #define WINHELP_CTX_ssh_bugs_rekey2 "config-ssh-bug-rekey" #define WINHELP_CTX_ssh_bugs_maxpkt2 "config-ssh-bug-maxpkt2" +#define WINHELP_CTX_ssh_bugs_rsa_sha2_cert_userauth "config-ssh-bug-rsa-sha2-cert-userauth" #define WINHELP_CTX_ssh_bugs_winadj "config-ssh-bug-winadj" #define WINHELP_CTX_ssh_bugs_chanreq "config-ssh-bug-chanreq" #define WINHELP_CTX_ssh_bugs_oldgex2 "config-ssh-bug-oldgex2"