Переглянути джерело

Merge branch 'thirdparty_dev' into dev

# Conflicts:
#	source/putty/WINDOWS/winmisc.c
#	source/putty/WINDOWS/winnet.c
#	source/putty/WINDOWS/winpgntc.c
#	source/putty/WINDOWS/winsecur.c
#	source/putty/WINDOWS/winstuff.h
#	source/putty/callback.c

#	source/putty/mpint.c
#	source/putty/ssh.c
#	source/putty/ssh.h
#	source/putty/ssh1connection.c
#	source/putty/ssh1login.c
#	source/putty/ssh2connection.c
#	source/putty/ssh2transport.c
#	source/putty/ssh2transport.h
#	source/putty/ssh2userauth.c
#	source/putty/sshaes.c
#	source/putty/sshauxcrypt.c
#	source/putty/sshcrcda.c
#	source/putty/sshecc.c
#	source/putty/sshhmac.c
#	source/putty/sshmd5.c
#	source/putty/sshprng.c
#	source/putty/sshpubk.c
#	source/putty/sshrand.c
#	source/putty/sshsh256.c
#	source/putty/sshsha.c

Source commit: 38ca28095fe64239d0915b7691a010006d1fed98
Martin Prikryl 4 роки тому
батько
коміт
05d66aa6f3
88 змінених файлів з 6667 додано та 2769 видалено
  1. 23 23
      source/putty/agentf.c
  2. 22 19
      source/putty/be_misc.c
  3. 6 4
      source/putty/be_ssh.c
  4. 3 3
      source/putty/callback.c
  5. 5 0
      source/putty/defs.h
  6. 8 3
      source/putty/ecc.c
  7. 5 0
      source/putty/ecc.h
  8. 7 10
      source/putty/errsock.c
  9. 97 91
      source/putty/import.c
  10. 1 1
      source/putty/logging.c
  11. 23 24
      source/putty/mainchan.c
  12. 47 0
      source/putty/marshal.c
  13. 9 0
      source/putty/marshal.h
  14. 54 12
      source/putty/misc.c
  15. 37 0
      source/putty/misc.h
  16. 333 111
      source/putty/mpint.c
  17. 44 2
      source/putty/mpint.h
  18. 3 0
      source/putty/mpint_i.h
  19. 29 13
      source/putty/network.h
  20. 6 7
      source/putty/nullplug.c
  21. 150 39
      source/putty/pageant.h
  22. 39 42
      source/putty/portfwd.c
  23. 72 76
      source/putty/proxy.c
  24. 343 140
      source/putty/putty.h
  25. 28 8
      source/putty/settings.c
  26. 104 51
      source/putty/ssh.c
  27. 193 69
      source/putty/ssh.h
  28. 7 7
      source/putty/ssh1bpp.c
  29. 45 31
      source/putty/ssh1connection-client.c
  30. 36 66
      source/putty/ssh1connection.c
  31. 0 4
      source/putty/ssh1connection.h
  32. 32 32
      source/putty/ssh1login.c
  33. 21 7
      source/putty/ssh2bpp-bare.c
  34. 64 7
      source/putty/ssh2bpp.c
  35. 23 3
      source/putty/ssh2connection-client.c
  36. 71 72
      source/putty/ssh2connection.c
  37. 1 3
      source/putty/ssh2connection.h
  38. 33 21
      source/putty/ssh2kex-client.c
  39. 163 27
      source/putty/ssh2transport.c
  40. 7 1
      source/putty/ssh2transport.h
  41. 60 32
      source/putty/ssh2userauth.c
  42. 46 17
      source/putty/sshaes.c
  43. 24 10
      source/putty/ssharcf.c
  44. 593 0
      source/putty/sshargon2.c
  45. 5 7
      source/putty/sshauxcrypt.c
  46. 242 0
      source/putty/sshblake2.c
  47. 35 18
      source/putty/sshblowf.c
  48. 5 0
      source/putty/sshbpp.h
  49. 26 19
      source/putty/sshccp.c
  50. 4 0
      source/putty/sshchan.h
  51. 41 158
      source/putty/sshcommon.c
  52. 7 5
      source/putty/sshcr.h
  53. 2 8
      source/putty/sshcrcda.c
  54. 60 16
      source/putty/sshdes.c
  55. 34 21
      source/putty/sshdss.c
  56. 268 125
      source/putty/sshecc.c
  57. 78 65
      source/putty/sshhmac.c
  58. 190 223
      source/putty/sshmd5.c
  59. 1 0
      source/putty/sshppl.h
  60. 30 35
      source/putty/sshprng.c
  61. 448 300
      source/putty/sshpubk.c
  62. 3 3
      source/putty/sshrand.c
  63. 108 37
      source/putty/sshrsa.c
  64. 82 51
      source/putty/sshsh256.c
  65. 752 285
      source/putty/sshsh512.c
  66. 81 51
      source/putty/sshsha.c
  67. 357 0
      source/putty/sshsha3.c
  68. 12 12
      source/putty/sshshare.c
  69. 128 0
      source/putty/sshutils.c
  70. 7 7
      source/putty/sshverstring.c
  71. 10 10
      source/putty/sshzlib.c
  72. 8 8
      source/putty/tree234.c
  73. 73 2
      source/putty/utils.c
  74. 5 5
      source/putty/version.h
  75. 93 0
      source/putty/windows/wincapi.c
  76. 31 0
      source/putty/windows/wincapi.h
  77. 1 1
      source/putty/windows/winhandl.c
  78. 9 9
      source/putty/windows/winhsock.c
  79. 3 5
      source/putty/windows/winmisc.c
  80. 8 0
      source/putty/windows/winmiscs.c
  81. 75 77
      source/putty/windows/winnet.c
  82. 98 0
      source/putty/windows/winnpc.c
  83. 220 12
      source/putty/windows/winpgntc.c
  84. 1 1
      source/putty/windows/winproxy.c
  85. 13 3
      source/putty/windows/winsecur.c
  86. 7 11
      source/putty/windows/winsecur.h
  87. 61 62
      source/putty/windows/winstuff.h
  88. 28 29
      source/putty/x11fwd.c

+ 23 - 23
source/putty/agentf.c

@@ -147,29 +147,29 @@ static void agentf_send_eof(Channel *chan);
 static char *agentf_log_close_msg(Channel *chan);
 static void agentf_set_input_wanted(Channel *chan, bool wanted);
 
-static const struct ChannelVtable agentf_channelvt = {
-    agentf_free,
-    chan_remotely_opened_confirmation,
-    chan_remotely_opened_failure,
-    agentf_send,
-    agentf_send_eof,
-    agentf_set_input_wanted,
-    agentf_log_close_msg,
-    chan_default_want_close,
-    chan_no_exit_status,
-    chan_no_exit_signal,
-    chan_no_exit_signal_numeric,
-    chan_no_run_shell,
-    chan_no_run_command,
-    chan_no_run_subsystem,
-    chan_no_enable_x11_forwarding,
-    chan_no_enable_agent_forwarding,
-    chan_no_allocate_pty,
-    chan_no_set_env,
-    chan_no_send_break,
-    chan_no_send_signal,
-    chan_no_change_window_size,
-    chan_no_request_response,
+static const ChannelVtable agentf_channelvt = {
+    .free = agentf_free,
+    .open_confirmation = chan_remotely_opened_confirmation,
+    .open_failed = chan_remotely_opened_failure,
+    .send = agentf_send,
+    .send_eof = agentf_send_eof,
+    .set_input_wanted = agentf_set_input_wanted,
+    .log_close_msg = agentf_log_close_msg,
+    .want_close = chan_default_want_close,
+    .rcvd_exit_status = chan_no_exit_status,
+    .rcvd_exit_signal = chan_no_exit_signal,
+    .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
+    .run_shell = chan_no_run_shell,
+    .run_command = chan_no_run_command,
+    .run_subsystem = chan_no_run_subsystem,
+    .enable_x11_forwarding = chan_no_enable_x11_forwarding,
+    .enable_agent_forwarding = chan_no_enable_agent_forwarding,
+    .allocate_pty = chan_no_allocate_pty,
+    .set_env = chan_no_set_env,
+    .send_break = chan_no_send_break,
+    .send_signal = chan_no_send_signal,
+    .change_window_size = chan_no_change_window_size,
+    .request_response = chan_no_request_response,
 };
 
 Channel *agentf_new(SshChannel *c)

+ 22 - 19
source/putty/be_misc.c

@@ -9,14 +9,14 @@
 #include "network.h"
 
 void backend_socket_log(Seat *seat, LogContext *logctx,
-                        int type, SockAddr *addr, int port,
+                        PlugLogType type, SockAddr *addr, int port,
                         const char *error_msg, int error_code, Conf *conf,
                         bool session_started)
 {
     char addrbuf[256], *msg;
 
     switch (type) {
-      case 0:
+      case PLUGLOG_CONNECT_TRYING:
         sk_getaddr(addr, addrbuf, lenof(addrbuf));
         if (sk_addr_needs_port(addr)) {
             msg = dupprintf("Connecting to %s port %d", addrbuf, port);
@@ -24,30 +24,33 @@ void backend_socket_log(Seat *seat, LogContext *logctx,
             msg = dupprintf("Connecting to %s", addrbuf);
         }
         break;
-      case 1:
+      case PLUGLOG_CONNECT_FAILED:
         sk_getaddr(addr, addrbuf, lenof(addrbuf));
         msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg);
         break;
-      case 2:
+      case PLUGLOG_CONNECT_SUCCESS:
+        sk_getaddr(addr, addrbuf, lenof(addrbuf));
+        msg = dupprintf("Connected to %s", addrbuf);
+        break;
+      case PLUGLOG_PROXY_MSG: {
         /* Proxy-related log messages have their own identifying
          * prefix already, put on by our caller. */
-        {
-            int len, log_to_term;
+        int len, log_to_term;
 
-            /* Suffix \r\n temporarily, so we can log to the terminal. */
-            msg = dupprintf("%s\r\n", error_msg);
-            len = strlen(msg);
-            assert(len >= 2);
+        /* Suffix \r\n temporarily, so we can log to the terminal. */
+        msg = dupprintf("%s\r\n", error_msg);
+        len = strlen(msg);
+        assert(len >= 2);
 
-            log_to_term = conf_get_int(conf, CONF_proxy_log_to_term);
-            if (log_to_term == AUTO)
-                log_to_term = session_started ? FORCE_OFF : FORCE_ON;
-            if (log_to_term == FORCE_ON)
-                seat_stderr(seat, msg, len);
+        log_to_term = conf_get_int(conf, CONF_proxy_log_to_term);
+        if (log_to_term == AUTO)
+            log_to_term = session_started ? FORCE_OFF : FORCE_ON;
+        if (log_to_term == FORCE_ON)
+            seat_stderr(seat, msg, len);
 
-            msg[len-2] = '\0';         /* remove the \r\n again */
-        }
+        msg[len-2] = '\0';         /* remove the \r\n again */
         break;
+      }
       default:
         msg = NULL;  /* shouldn't happen, but placate optimiser */
         break;
@@ -117,7 +120,7 @@ void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb,
             { // WINSCP
             char *msg = dupprintf(
                 "proxy: %.*s", (int)(endpos - pos), psb->buf + pos);
-            plug_log(plug, 2, NULL, 0, msg, 0);
+            plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0);
             sfree(msg);
 
             pos = nlpos - psb->buf + 1;
@@ -134,7 +137,7 @@ void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb,
         if (pos == 0 && psb->size == lenof(psb->buf)) {
             char *msg = dupprintf(
                 "proxy (partial line): %.*s", (int)psb->size, psb->buf);
-            plug_log(plug, 2, NULL, 0, msg, 0);
+            plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0);
             sfree(msg);
 
             pos = psb->size = 0;

+ 6 - 4
source/putty/be_ssh.c

@@ -1,8 +1,7 @@
 /*
- * Linking module for programs that are restricted to only using SSH
- * (pscp and psftp). These do not support selection of backend, but
- * must still have a backends[] array mentioning SSH because
- * settings.c will want to consult it during session load.
+ * Linking module for programs that are restricted to only using
+ * SSH-type protocols (pscp and psftp). These still have a choice of
+ * two actual backends, because they can also speak PROT_SSHCONN.
  */
 
 #include <stdio.h>
@@ -12,5 +11,8 @@ const int be_default_protocol = PROT_SSH;
 
 const struct BackendVtable *const backends[] = {
     &ssh_backend,
+    &sshconn_backend,
     NULL
 };
+
+const size_t n_ui_backends = 0;  /* not used in programs with a config UI */

+ 3 - 3
source/putty/callback.c

@@ -24,12 +24,12 @@ struct callback {
 #define cbhead CALLBACK_SET_VAR->cbhead
 #define cbtail CALLBACK_SET_VAR->cbtail
 #else
-struct callback *cbcurr = NULL, *cbhead = NULL, *cbtail = NULL;
+static struct callback *cbcurr = NULL, *cbhead = NULL, *cbtail = NULL;
 #endif
 
 #ifndef MPEXT
-toplevel_callback_notify_fn_t notify_frontend = NULL;
-void *notify_ctx = NULL;
+static toplevel_callback_notify_fn_t notify_frontend = NULL;
+static void *notify_ctx = NULL;
 
 void request_callback_notifications(toplevel_callback_notify_fn_t fn,
                                     void *ctx)

+ 5 - 0
source/putty/defs.h

@@ -13,11 +13,13 @@
 
 #include <stddef.h>
 #include <stdint.h>
+#include <stdio.h>                     /* for __MINGW_PRINTF_FORMAT */
 #include <stdbool.h>
 
 #if (!defined WINSCP) && defined _MSC_VER && _MSC_VER < 1800
 /* Work around lack of inttypes.h and strtoumax in older MSVC */
 #define PRIx32 "x"
+#define PRIu32 "u"
 #define PRIu64 "I64u"
 #define PRIdMAX "I64d"
 #define PRIXMAX "I64X"
@@ -67,6 +69,7 @@ typedef struct FontSpec FontSpec;
 typedef struct bufchain_tag bufchain;
 
 typedef struct strbuf strbuf;
+typedef struct LoadedFile LoadedFile;
 
 typedef struct RSAKey RSAKey;
 
@@ -184,6 +187,8 @@ typedef struct PacketProtocolLayer PacketProtocolLayer;
 
 #if defined __GNUC__ || defined __clang__
 #define NORETURN __attribute__((__noreturn__))
+#elif defined _MSC_VER
+#define NORETURN __declspec(noreturn)
 #else
 #define NORETURN
 #endif

+ 8 - 3
source/putty/ecc.c

@@ -507,10 +507,10 @@ static void ecc_weierstrass_normalise(WeierstrassPoint *wp)
     mp_int *zinv3 = monty_mul(wc->mc, zinv2, zinv);
     monty_mul_into(wc->mc, wp->X, wp->X, zinv2);
     monty_mul_into(wc->mc, wp->Y, wp->Y, zinv3);
+    monty_mul_into(wc->mc, wp->Z, wp->Z, zinv);
     mp_free(zinv);
     mp_free(zinv2);
     mp_free(zinv3);
-    mp_copy_into(wp->Z, monty_identity(wc->mc));
 }
 
 void ecc_weierstrass_get_affine(
@@ -788,8 +788,8 @@ static void ecc_montgomery_normalise(MontgomeryPoint *mp)
     MontgomeryCurve *mc = mp->mc;
     mp_int *zinv = monty_invert(mc->mc, mp->Z);
     monty_mul_into(mc->mc, mp->X, mp->X, zinv);
+    monty_mul_into(mc->mc, mp->Z, mp->Z, zinv);
     mp_free(zinv);
-    mp_copy_into(mp->Z, monty_identity(mc->mc));
 }
 
 MontgomeryPoint *ecc_montgomery_multiply(MontgomeryPoint *B, mp_int *n)
@@ -865,6 +865,11 @@ void ecc_montgomery_get_affine(MontgomeryPoint *mp, mp_int **x)
         *x = monty_export(mc->mc, mp->X);
 }
 
+unsigned ecc_montgomery_is_identity(MontgomeryPoint *mp)
+{
+    return mp_eq_integer(mp->Z, 0);
+}
+
 /* ----------------------------------------------------------------------
  * Twisted Edwards curves.
  */
@@ -1119,8 +1124,8 @@ static void ecc_edwards_normalise(EdwardsPoint *ep)
     mp_int *zinv = monty_invert(ec->mc, ep->Z);
     monty_mul_into(ec->mc, ep->X, ep->X, zinv);
     monty_mul_into(ec->mc, ep->Y, ep->Y, zinv);
+    monty_mul_into(ec->mc, ep->Z, ep->Z, zinv);
     mp_free(zinv);
-    mp_copy_into(ep->Z, monty_identity(ec->mc));
     monty_mul_into(ec->mc, ep->T, ep->X, ep->Y);
 }
 

+ 5 - 0
source/putty/ecc.h

@@ -170,6 +170,11 @@ MontgomeryPoint *ecc_montgomery_multiply(MontgomeryPoint *, mp_int *);
  */
 void ecc_montgomery_get_affine(MontgomeryPoint *mp, mp_int **x);
 
+/*
+ * Test whether a point is the curve identity.
+ */
+unsigned ecc_montgomery_is_identity(MontgomeryPoint *mp);
+
 /* ----------------------------------------------------------------------
  * Twisted Edwards curves.
  *

+ 7 - 10
source/putty/errsock.c

@@ -45,17 +45,14 @@ static SocketPeerInfo *sk_error_peer_info(Socket *s)
 }
 
 static const SocketVtable ErrorSocket_sockvt = {
-    sk_error_plug,
-    sk_error_close,
-    NULL /* write */,
-    NULL /* write_oob */,
-    NULL /* write_eof */,
-    NULL /* set_frozen */,
-    sk_error_socket_error,
-    sk_error_peer_info,
+    .plug = sk_error_plug,
+    .close = sk_error_close,
+    .socket_error = sk_error_socket_error,
+    .peer_info = sk_error_peer_info,
+    /* other methods are NULL */
 };
 
-static Socket *new_error_socket_internal(char *errmsg, Plug *plug)
+Socket *new_error_socket_consume_string(Plug *plug, char *errmsg)
 {
     ErrorSocket *es = snew(ErrorSocket);
     es->sock.vt = &ErrorSocket_sockvt;
@@ -73,5 +70,5 @@ Socket *new_error_socket_fmt(Plug *plug, const char *fmt, ...)
     msg = dupvprintf(fmt, ap);
     va_end(ap);
 
-    return new_error_socket_internal(msg, plug);
+    return new_error_socket_consume_string(plug, msg);
 }

+ 97 - 91
source/putty/import.c

@@ -13,12 +13,12 @@
 #include "mpint.h"
 #include "misc.h"
 
-static bool openssh_pem_encrypted(const Filename *file);
-static bool openssh_new_encrypted(const Filename *file);
+static bool openssh_pem_encrypted(BinarySource *src);
+static bool openssh_new_encrypted(BinarySource *src);
 static ssh2_userkey *openssh_pem_read(
-    const Filename *file, const char *passphrase, const char **errmsg_p);
+    BinarySource *src, const char *passphrase, const char **errmsg_p);
 static ssh2_userkey *openssh_new_read(
-    const Filename *file, const char *passphrase, const char **errmsg_p);
+    BinarySource *src, const char *passphrase, const char **errmsg_p);
 static bool openssh_auto_write(
     const Filename *file, ssh2_userkey *key, const char *passphrase);
 static bool openssh_pem_write(
@@ -26,9 +26,9 @@ static bool openssh_pem_write(
 static bool openssh_new_write(
     const Filename *file, ssh2_userkey *key, const char *passphrase);
 
-static bool sshcom_encrypted(const Filename *file, char **comment);
+static bool sshcom_encrypted(BinarySource *src, char **comment);
 static ssh2_userkey *sshcom_read(
-    const Filename *file, const char *passphrase, const char **errmsg_p);
+    BinarySource *src, const char *passphrase, const char **errmsg_p);
 static bool sshcom_write(
     const Filename *file, ssh2_userkey *key, const char *passphrase);
 
@@ -59,50 +59,97 @@ int import_target_type(int type)
     return SSH_KEYTYPE_SSH2;
 }
 
+static inline char *bsgetline(BinarySource *src)
+{
+    ptrlen line = get_chomped_line(src);
+    if (get_err(src))
+        return NULL;
+    return mkstr(line);
+}
+
 /*
  * Determine whether a foreign key is encrypted.
  */
-bool import_encrypted(const Filename *filename, int type, char **comment)
+bool import_encrypted_s(const Filename *filename, BinarySource *src,
+                        int type, char **comment)
 {
     if (type == SSH_KEYTYPE_OPENSSH_PEM) {
         /* OpenSSH PEM format doesn't contain a key comment at all */
         *comment = dupstr(filename_to_str(filename));
-        return openssh_pem_encrypted(filename);
+        return openssh_pem_encrypted(src);
     } else if (type == SSH_KEYTYPE_OPENSSH_NEW) {
         /* OpenSSH new format does, but it's inside the encrypted
          * section for some reason */
         *comment = dupstr(filename_to_str(filename));
-        return openssh_new_encrypted(filename);
+        return openssh_new_encrypted(src);
     } else if (type == SSH_KEYTYPE_SSHCOM) {
-        return sshcom_encrypted(filename, comment);
+        return sshcom_encrypted(src, comment);
     }
     return false;
 }
 
+bool import_encrypted(const Filename *filename, int type, char **comment)
+{
+    LoadedFile *lf = lf_load_keyfile(filename, NULL);
+    if (!lf)
+        return false; /* couldn't even open the file */
+
+    bool toret = import_encrypted_s(filename, BinarySource_UPCAST(lf),
+                                    type, comment);
+    lf_free(lf);
+    return toret;
+}
+
 /*
  * Import an SSH-1 key.
  */
+int import_ssh1_s(BinarySource *src, int type,
+                  RSAKey *key, char *passphrase, const char **errmsg_p)
+{
+    return 0;
+}
+
 int import_ssh1(const Filename *filename, int type,
                 RSAKey *key, char *passphrase, const char **errmsg_p)
 {
-    return 0;
+    LoadedFile *lf = lf_load_keyfile(filename, errmsg_p);
+    if (!lf)
+        return false;
+
+    int toret = import_ssh1_s(BinarySource_UPCAST(lf),
+                              type, key, passphrase, errmsg_p);
+    lf_free(lf);
+    return toret;
 }
 
 /*
  * Import an SSH-2 key.
  */
-ssh2_userkey *import_ssh2(const Filename *filename, int type,
-                                 char *passphrase, const char **errmsg_p)
+ssh2_userkey *import_ssh2_s(BinarySource *src, int type,
+                            char *passphrase, const char **errmsg_p)
 {
     if (type == SSH_KEYTYPE_OPENSSH_PEM)
-        return openssh_pem_read(filename, passphrase, errmsg_p);
+        return openssh_pem_read(src, passphrase, errmsg_p);
     else if (type == SSH_KEYTYPE_OPENSSH_NEW)
-        return openssh_new_read(filename, passphrase, errmsg_p);
+        return openssh_new_read(src, passphrase, errmsg_p);
     if (type == SSH_KEYTYPE_SSHCOM)
-        return sshcom_read(filename, passphrase, errmsg_p);
+        return sshcom_read(src, passphrase, errmsg_p);
     return NULL;
 }
 
+ssh2_userkey *import_ssh2(const Filename *filename, int type,
+                          char *passphrase, const char **errmsg_p)
+{
+    LoadedFile *lf = lf_load_keyfile(filename, errmsg_p);
+    if (!lf)
+        return false;
+
+    ssh2_userkey *toret = import_ssh2_s(BinarySource_UPCAST(lf),
+                                        type, passphrase, errmsg_p);
+    lf_free(lf);
+    return toret;
+}
+
 /*
  * Export an SSH-1 key.
  */
@@ -300,11 +347,10 @@ void BinarySink_put_mp_ssh2_from_string(BinarySink *bs, ptrlen str)
 #define put_mp_ssh2_from_string(bs, str) \
     BinarySink_put_mp_ssh2_from_string(BinarySink_UPCAST(bs), str)
 
-static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename,
+static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src,
                                                     const char **errmsg_p)
 {
     struct openssh_pem_key *ret;
-    FILE *fp = NULL;
     char *line = NULL;
     const char *errmsg;
     char *p;
@@ -315,17 +361,10 @@ static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename,
     ret = snew(struct openssh_pem_key);
     ret->keyblob = strbuf_new_nm();
 
-    fp = f_open(filename, "r", false);
-    if (!fp) {
-        errmsg = "unable to open key file";
-        goto error;
-    }
-
-    if (!(line = fgetline(fp))) {
+    if (!(line = bsgetline(src))) {
         errmsg = "unexpected end of file";
         goto error;
     }
-    strip_crlf(line);
     if (!strstartswith(line, "-----BEGIN ") ||
         !strendswith(line, "PRIVATE KEY-----")) {
         errmsg = "file does not begin with OpenSSH key header";
@@ -359,11 +398,10 @@ static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename,
 
     headers_done = false;
     while (1) {
-        if (!(line = fgetline(fp))) {
+        if (!(line = bsgetline(src))) {
             errmsg = "unexpected end of file";
             goto error;
         }
-        strip_crlf(line);
         if (strstartswith(line, "-----END ") &&
             strendswith(line, "PRIVATE KEY-----")) {
             sfree(line);
@@ -445,9 +483,6 @@ static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename,
         line = NULL;
     }
 
-    fclose(fp);
-    fp = NULL;
-
     if (!ret->keyblob || ret->keyblob->len == 0) {
         errmsg = "key body not present";
         goto error;
@@ -477,13 +512,12 @@ static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename,
         sfree(ret);
     }
     if (errmsg_p) *errmsg_p = errmsg;
-    if (fp) fclose(fp);
     return NULL;
 }
 
-static bool openssh_pem_encrypted(const Filename *filename)
+static bool openssh_pem_encrypted(BinarySource *src)
 {
-    struct openssh_pem_key *key = load_openssh_pem_key(filename, NULL);
+    struct openssh_pem_key *key = load_openssh_pem_key(src, NULL);
     bool ret;
 
     if (!key)
@@ -516,9 +550,9 @@ static void openssh_pem_derivekey(
     h = ssh_hash_new(&ssh_md5);
     put_datapl(h, passphrase);
     put_data(h, iv, 8);
-    ssh_hash_final(h, keybuf);
+    ssh_hash_digest(h, keybuf);
 
-    h = ssh_hash_new(&ssh_md5);
+    ssh_hash_reset(h);
     put_data(h, keybuf, 16);
     put_datapl(h, passphrase);
     put_data(h, iv, 8);
@@ -526,9 +560,9 @@ static void openssh_pem_derivekey(
 }
 
 static ssh2_userkey *openssh_pem_read(
-    const Filename *filename, const char *passphrase, const char **errmsg_p)
+    BinarySource *filesrc, const char *passphrase, const char **errmsg_p)
 {
-    struct openssh_pem_key *key = load_openssh_pem_key(filename, errmsg_p);
+    struct openssh_pem_key *key = load_openssh_pem_key(filesrc, errmsg_p);
     ssh2_userkey *retkey;
     const ssh_keyalg *alg;
     BinarySource src[1];
@@ -1091,11 +1125,10 @@ struct openssh_new_key {
     strbuf *keyblob;
 };
 
-static struct openssh_new_key *load_openssh_new_key(const Filename *filename,
+static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
                                                     const char **errmsg_p)
 {
     struct openssh_new_key *ret;
-    FILE *fp = NULL;
     char *line = NULL;
     const char *errmsg;
     char *p;
@@ -1108,17 +1141,10 @@ static struct openssh_new_key *load_openssh_new_key(const Filename *filename,
     ret = snew(struct openssh_new_key);
     ret->keyblob = strbuf_new_nm();
 
-    fp = f_open(filename, "r", false);
-    if (!fp) {
-        errmsg = "unable to open key file";
-        goto error;
-    }
-
-    if (!(line = fgetline(fp))) {
+    if (!(line = bsgetline(filesrc))) {
         errmsg = "unexpected end of file";
         goto error;
     }
-    strip_crlf(line);
     if (0 != strcmp(line, "-----BEGIN OPENSSH PRIVATE KEY-----")) {
         errmsg = "file does not begin with OpenSSH new-style key header";
         goto error;
@@ -1128,11 +1154,10 @@ static struct openssh_new_key *load_openssh_new_key(const Filename *filename,
     line = NULL;
 
     while (1) {
-        if (!(line = fgetline(fp))) {
+        if (!(line = bsgetline(filesrc))) {
             errmsg = "unexpected end of file";
             goto error;
         }
-        strip_crlf(line);
         if (0 == strcmp(line, "-----END OPENSSH PRIVATE KEY-----")) {
             sfree(line);
             line = NULL;
@@ -1167,9 +1192,6 @@ static struct openssh_new_key *load_openssh_new_key(const Filename *filename,
         line = NULL;
     }
 
-    fclose(fp);
-    fp = NULL;
-
     if (ret->keyblob->len == 0) {
         errmsg = "key body not present";
         goto error;
@@ -1217,20 +1239,19 @@ static struct openssh_new_key *load_openssh_new_key(const Filename *filename,
             goto error;
         }
         break;
-      case ON_K_BCRYPT:
-        {
-            BinarySource opts[1];
+      case ON_K_BCRYPT: {
+        BinarySource opts[1];
 
-            BinarySource_BARE_INIT_PL(opts, str);
-            ret->kdfopts.bcrypt.salt = get_string(opts);
-            ret->kdfopts.bcrypt.rounds = get_uint32(opts);
+        BinarySource_BARE_INIT_PL(opts, str);
+        ret->kdfopts.bcrypt.salt = get_string(opts);
+        ret->kdfopts.bcrypt.rounds = get_uint32(opts);
 
-            if (get_err(opts)) {
-                errmsg = "failed to parse bcrypt options string";
-                goto error;
-            }
+        if (get_err(opts)) {
+          errmsg = "failed to parse bcrypt options string";
+          goto error;
         }
         break;
+      }
     }
 
     /*
@@ -1288,13 +1309,12 @@ static struct openssh_new_key *load_openssh_new_key(const Filename *filename,
         sfree(ret);
     }
     if (errmsg_p) *errmsg_p = errmsg;
-    if (fp) fclose(fp);
     return NULL;
 }
 
-static bool openssh_new_encrypted(const Filename *filename)
+static bool openssh_new_encrypted(BinarySource *src)
 {
-    struct openssh_new_key *key = load_openssh_new_key(filename, NULL);
+    struct openssh_new_key *key = load_openssh_new_key(src, NULL);
     bool ret;
 
     if (!key)
@@ -1307,9 +1327,9 @@ static bool openssh_new_encrypted(const Filename *filename)
 }
 
 static ssh2_userkey *openssh_new_read(
-    const Filename *filename, const char *passphrase, const char **errmsg_p)
+    BinarySource *filesrc, const char *passphrase, const char **errmsg_p)
 {
-    struct openssh_new_key *key = load_openssh_new_key(filename, errmsg_p);
+    struct openssh_new_key *key = load_openssh_new_key(filesrc, errmsg_p);
     ssh2_userkey *retkey = NULL;
     ssh2_userkey *retval = NULL;
     const char *errmsg;
@@ -1707,11 +1727,10 @@ struct sshcom_key {
     strbuf *keyblob;
 };
 
-static struct sshcom_key *load_sshcom_key(const Filename *filename,
+static struct sshcom_key *load_sshcom_key(BinarySource *src,
                                           const char **errmsg_p)
 {
     struct sshcom_key *ret;
-    FILE *fp;
     char *line = NULL;
     int hdrstart, len;
     const char *errmsg;
@@ -1724,16 +1743,10 @@ static struct sshcom_key *load_sshcom_key(const Filename *filename,
     ret->comment[0] = '\0';
     ret->keyblob = strbuf_new_nm();
 
-    fp = f_open(filename, "r", false);
-    if (!fp) {
-        errmsg = "unable to open key file";
-        goto error;
-    }
-    if (!(line = fgetline(fp))) {
+    if (!(line = bsgetline(src))) {
         errmsg = "unexpected end of file";
         goto error;
     }
-    strip_crlf(line);
     if (0 != strcmp(line, "---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----")) {
         errmsg = "file does not begin with ssh.com key header";
         goto error;
@@ -1744,11 +1757,10 @@ static struct sshcom_key *load_sshcom_key(const Filename *filename,
 
     headers_done = false;
     while (1) {
-        if (!(line = fgetline(fp))) {
+        if (!(line = bsgetline(src))) {
             errmsg = "unexpected end of file";
             goto error;
         }
-        strip_crlf(line);
         if (!strcmp(line, "---- END SSH2 ENCRYPTED PRIVATE KEY ----")) {
             sfree(line);
             line = NULL;
@@ -1773,12 +1785,11 @@ static struct sshcom_key *load_sshcom_key(const Filename *filename,
                 char *line2;
                 int line2len;
 
-                line2 = fgetline(fp);
+                line2 = bsgetline(src);
                 if (!line2) {
                     errmsg = "unexpected end of file";
                     goto error;
                 }
-                strip_crlf(line2);
 
                 line2len = strlen(line2);
                 line = sresize(line, len + line2len + 1, char);
@@ -1791,7 +1802,6 @@ static struct sshcom_key *load_sshcom_key(const Filename *filename,
                 line2 = NULL;
             }
             p = line + hdrstart;
-            strip_crlf(p);
             if (!strcmp(line, "Comment")) {
                 /* Strip quotes in comment if present. */
                 if (p[0] == '"' && p[strlen(p)-1] == '"') {
@@ -1835,14 +1845,10 @@ static struct sshcom_key *load_sshcom_key(const Filename *filename,
         goto error;
     }
 
-    fclose(fp);
     if (errmsg_p) *errmsg_p = NULL;
     return ret;
 
     error:
-    if (fp)
-        fclose(fp);
-
     if (line) {
         smemclr(line, strlen(line));
         sfree(line);
@@ -1857,9 +1863,9 @@ static struct sshcom_key *load_sshcom_key(const Filename *filename,
     return NULL;
 }
 
-static bool sshcom_encrypted(const Filename *filename, char **comment)
+static bool sshcom_encrypted(BinarySource *filesrc, char **comment)
 {
-    struct sshcom_key *key = load_sshcom_key(filename, NULL);
+    struct sshcom_key *key = load_sshcom_key(filesrc, NULL);
     BinarySource src[1];
     ptrlen str;
     bool answer = false;
@@ -1936,15 +1942,15 @@ static void sshcom_derivekey(ptrlen passphrase, uint8_t *keybuf)
 
     h = ssh_hash_new(&ssh_md5);
     put_datapl(h, passphrase);
-    ssh_hash_final(ssh_hash_copy(h), keybuf);
+    ssh_hash_digest_nondestructive(h, keybuf);
     put_data(h, keybuf, 16);
     ssh_hash_final(h, keybuf + 16);
 }
 
 static ssh2_userkey *sshcom_read(
-    const Filename *filename, const char *passphrase, const char **errmsg_p)
+    BinarySource *filesrc, const char *passphrase, const char **errmsg_p)
 {
-    struct sshcom_key *key = load_sshcom_key(filename, errmsg_p);
+    struct sshcom_key *key = load_sshcom_key(filesrc, errmsg_p);
     const char *errmsg;
     BinarySource src[1];
     ptrlen str, ciphertext;

+ 1 - 1
source/putty/logging.c

@@ -364,7 +364,7 @@ void log_packet(LogContext *ctx, int direction, int type,
                 c = 'X';
                 sprintf(smalldata, "XX");
             } else {  /* PKTLOG_EMIT */
-                c = ((unsigned char *)data)[p];
+                c = ((const unsigned char *)data)[p];
                 sprintf(smalldata, "%02x", c);
             }
             dumpdata[10+2+3*(p%16)] = smalldata[0];

+ 23 - 24
source/putty/mainchan.c

@@ -26,29 +26,29 @@ static bool mainchan_rcvd_exit_signal_numeric(
     Channel *chan, int signum, bool core_dumped, ptrlen msg);
 static void mainchan_request_response(Channel *chan, bool success);
 
-static const struct ChannelVtable mainchan_channelvt = {
-    mainchan_free,
-    mainchan_open_confirmation,
-    mainchan_open_failure,
-    mainchan_send,
-    mainchan_send_eof,
-    mainchan_set_input_wanted,
-    mainchan_log_close_msg,
-    chan_default_want_close,
-    mainchan_rcvd_exit_status,
-    mainchan_rcvd_exit_signal,
-    mainchan_rcvd_exit_signal_numeric,
-    chan_no_run_shell,
-    chan_no_run_command,
-    chan_no_run_subsystem,
-    chan_no_enable_x11_forwarding,
-    chan_no_enable_agent_forwarding,
-    chan_no_allocate_pty,
-    chan_no_set_env,
-    chan_no_send_break,
-    chan_no_send_signal,
-    chan_no_change_window_size,
-    mainchan_request_response,
+static const ChannelVtable mainchan_channelvt = {
+    .free = mainchan_free,
+    .open_confirmation = mainchan_open_confirmation,
+    .open_failed = mainchan_open_failure,
+    .send = mainchan_send,
+    .send_eof = mainchan_send_eof,
+    .set_input_wanted = mainchan_set_input_wanted,
+    .log_close_msg = mainchan_log_close_msg,
+    .want_close = chan_default_want_close,
+    .rcvd_exit_status = mainchan_rcvd_exit_status,
+    .rcvd_exit_signal = mainchan_rcvd_exit_signal,
+    .rcvd_exit_signal_numeric = mainchan_rcvd_exit_signal_numeric,
+    .run_shell = chan_no_run_shell,
+    .run_command = chan_no_run_command,
+    .run_subsystem = chan_no_run_subsystem,
+    .enable_x11_forwarding = chan_no_enable_x11_forwarding,
+    .enable_agent_forwarding = chan_no_enable_agent_forwarding,
+    .allocate_pty = chan_no_allocate_pty,
+    .set_env = chan_no_set_env,
+    .send_break = chan_no_send_break,
+    .send_signal = chan_no_send_signal,
+    .change_window_size = chan_no_change_window_size,
+    .request_response = mainchan_request_response,
 };
 
 typedef enum MainChanType {
@@ -238,7 +238,6 @@ static void mainchan_request_response(Channel *chan, bool success)
 
         if (success) {
             ppl_logevent("Agent forwarding enabled");
-            ssh_enable_agent_fwd(mc->cl);
         } else {
             ppl_logevent("Agent forwarding refused");
         }

+ 47 - 0
source/putty/marshal.c

@@ -218,6 +218,53 @@ const char *BinarySource_get_asciz(BinarySource *src)
     return start;
 }
 
+static ptrlen BinarySource_get_chars_internal(
+    BinarySource *src, const char *set, bool include)
+{
+    const char *start = here;
+    while (avail(1)) {
+        bool present = NULL != strchr(set, *(const char *)consume(0));
+        if (present != include)
+            break;
+        (void) consume(1);
+    }
+    const char *end = here;
+    return make_ptrlen(start, end - start);
+}
+
+ptrlen BinarySource_get_chars(BinarySource *src, const char *include_set)
+{
+    return BinarySource_get_chars_internal(src, include_set, true);
+}
+
+ptrlen BinarySource_get_nonchars(BinarySource *src, const char *exclude_set)
+{
+    return BinarySource_get_chars_internal(src, exclude_set, false);
+}
+
+ptrlen BinarySource_get_chomped_line(BinarySource *src)
+{
+    const char *start, *end;
+
+    if (src->err)
+        return make_ptrlen(here, 0);
+
+    start = here;
+    end = memchr(start, '\n', src->len - src->pos);
+    if (end)
+        advance(end + 1 - start);
+    else
+        advance(src->len - src->pos);
+    end = here;
+
+    if (end > start && end[-1] == '\n')
+        end--;
+    if (end > start && end[-1] == '\r')
+        end--;
+
+    return make_ptrlen(start, end - start);
+}
+
 ptrlen BinarySource_get_pstring(BinarySource *src)
 {
     const unsigned char *ucp;

+ 9 - 0
source/putty/marshal.h

@@ -276,6 +276,12 @@ static inline void BinarySource_INIT__(BinarySource *src, ptrlen data)
     BinarySource_get_string(BinarySource_UPCAST(src))
 #define get_asciz(src) \
     BinarySource_get_asciz(BinarySource_UPCAST(src))
+#define get_chars(src, include) \
+    BinarySource_get_chars(BinarySource_UPCAST(src), include)
+#define get_nonchars(src, exclude) \
+    BinarySource_get_nonchars(BinarySource_UPCAST(src), exclude)
+#define get_chomped_line(src) \
+    BinarySource_get_chomped_line(BinarySource_UPCAST(src))
 #define get_pstring(src) \
     BinarySource_get_pstring(BinarySource_UPCAST(src))
 #define get_mp_ssh1(src) \
@@ -305,6 +311,9 @@ unsigned long BinarySource_get_uint32(BinarySource *);
 uint64_t BinarySource_get_uint64(BinarySource *);
 ptrlen BinarySource_get_string(BinarySource *);
 const char *BinarySource_get_asciz(BinarySource *);
+ptrlen BinarySource_get_chars(BinarySource *, const char *include_set);
+ptrlen BinarySource_get_nonchars(BinarySource *, const char *exclude_set);
+ptrlen BinarySource_get_chomped_line(BinarySource *);
 ptrlen BinarySource_get_pstring(BinarySource *);
 mp_int *BinarySource_get_mp_ssh1(BinarySource *src);
 mp_int *BinarySource_get_mp_ssh2(BinarySource *src);

+ 54 - 12
source/putty/misc.c

@@ -22,6 +22,10 @@
 #include "putty.h"
 #include "misc.h"
 
+#define BASE64_CHARS_NOEQ \
+    "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/"
+#define BASE64_CHARS_ALL BASE64_CHARS_NOEQ "="
+
 void seat_connection_fatal(Seat *seat, const char *fmt, ...)
 {
     va_list ap;
@@ -129,22 +133,32 @@ bool validate_manual_hostkey(char *key)
          * Now q is our word.
          */
 
-        if (strlen(q) == 16*3 - 1 &&
-            q[strspn(q, "0123456789abcdefABCDEF:")] == 0) {
+        if (strstartswith(q, "SHA256:")) {
+            /* Test for a valid SHA256 key fingerprint. */
+            r = q + 7;
+            if (strlen(r) == 43 && r[strspn(r, BASE64_CHARS_NOEQ)] == 0)
+                return true;
+        }
+
+        r = q;
+        if (strstartswith(r, "MD5:"))
+            r += 4;
+        if (strlen(r) == 16*3 - 1 &&
+            r[strspn(r, "0123456789abcdefABCDEF:")] == 0) {
             /*
-             * Might be a key fingerprint. Check the colons are in the
-             * right places, and if so, return the same fingerprint
-             * canonicalised into lowercase.
+             * Test for a valid MD5 key fingerprint. Check the colons
+             * are in the right places, and if so, return the same
+             * fingerprint canonicalised into lowercase.
              */
             int i;
             for (i = 0; i < 16; i++)
-                if (q[3*i] == ':' || q[3*i+1] == ':')
+                if (r[3*i] == ':' || r[3*i+1] == ':')
                     goto not_fingerprint; /* sorry */
             for (i = 0; i < 15; i++)
-                if (q[3*i+2] != ':')
+                if (r[3*i+2] != ':')
                     goto not_fingerprint; /* sorry */
             for (i = 0; i < 16*3 - 1; i++)
-                key[i] = tolower(q[i]);
+                key[i] = tolower(r[i]);
             key[16*3 - 1] = '\0';
             return true;
         }
@@ -161,8 +175,7 @@ bool validate_manual_hostkey(char *key)
         *s = '\0';
 
         if (strlen(q) % 4 == 0 && strlen(q) > 2*4 &&
-            q[strspn(q, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-                     "abcdefghijklmnopqrstuvwxyz+/=")] == 0) {
+            q[strspn(q, BASE64_CHARS_ALL)] == 0) {
             /*
              * Might be a base64-encoded SSH-2 public key blob. Check
              * that it starts with a sensible algorithm string. No
@@ -236,6 +249,27 @@ char *buildinfo(const char *newline)
      * anomalous first clause. That way the patch looks nicer when you
      * add extra ones.
      */
+#elif _MSC_VER == 1928 && _MSC_FULL_VER >= 192829500
+    /*
+     * 16.9 and 16.8 have the same _MSC_VER value, and have to be
+     * distinguished by _MSC_FULL_VER. As of 2021-03-04 that is not
+     * mentioned on the above page, but see e.g.
+     * https://developercommunity.visualstudio.com/t/the-169-cc-compiler-still-uses-the-same-version-nu/1335194#T-N1337120
+     * which says that 16.9 builds will have versions starting at
+     * 19.28.29500.* and going up. Hence, 19 28 29500 is what we
+     * compare _MSC_FULL_VER against above.
+     */
+    strbuf_catf(buf, " 2019 (16.9)");
+#elif _MSC_VER == 1928
+    strbuf_catf(buf, " 2019 (16.8)");
+#elif _MSC_VER == 1927
+    strbuf_catf(buf, " 2019 (16.7)");
+#elif _MSC_VER == 1926
+    strbuf_catf(buf, " 2019 (16.6)");
+#elif _MSC_VER == 1925
+    strbuf_catf(buf, " 2019 (16.5)");
+#elif _MSC_VER == 1924
+    strbuf_catf(buf, " 2019 (16.4)");
 #elif _MSC_VER == 1923
     strbuf_catf(buf, " 2019 (16.3)");
 #elif _MSC_VER == 1922
@@ -356,8 +390,8 @@ void nullseat_update_specials_menu(Seat *seat) {}
 char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; }
 void nullseat_set_busy_status(Seat *seat, BusyStatus status) {}
 int nullseat_verify_ssh_host_key(
-    Seat *seat, const char *host, int port,
-    const char *keytype, char *keystr, char *key_fingerprint,
+    Seat *seat, const char *host, int port, const char *keytype,
+    char *keystr, const char *keydisp, char **key_fingerprints,
     void (*callback)(void *ctx, int result), void *ctx) { return 0; }
 int nullseat_confirm_weak_crypto_primitive(
     Seat *seat, const char *algtype, const char *algname,
@@ -376,6 +410,14 @@ StripCtrlChars *nullseat_stripctrl_new(
     Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) {return NULL;}
 bool nullseat_set_trust_status(Seat *seat, bool tr) { return false; }
 bool nullseat_set_trust_status_vacuously(Seat *seat, bool tr) { return true; }
+bool nullseat_verbose_no(Seat *seat) { return false; }
+bool nullseat_verbose_yes(Seat *seat) { return true; }
+bool nullseat_interactive_no(Seat *seat) { return false; }
+bool nullseat_interactive_yes(Seat *seat) { return true; }
+bool nullseat_get_cursor_position(Seat *seat, int *x, int *y) { return false; }
+
+bool null_lp_verbose_no(LogPolicy *lp) { return false; }
+bool null_lp_verbose_yes(LogPolicy *lp) { return true; }
 
 void sk_free_peer_info(SocketPeerInfo *pi)
 {

+ 37 - 0
source/putty/misc.h

@@ -189,6 +189,10 @@ int string_length_for_printf(size_t);
  * string. */
 #define PTRLEN_LITERAL(stringlit) \
     TYPECHECK("" stringlit "", make_ptrlen(stringlit, sizeof(stringlit)-1))
+/* Make a ptrlen out of a compile-time string literal in a way that
+ * allows you to declare the ptrlen itself as a compile-time initialiser. */
+#define PTRLEN_DECL_LITERAL(stringlit) \
+    { TYPECHECK("" stringlit "", stringlit), sizeof(stringlit)-1 }
 /* Make a ptrlen out of a constant byte array. */
 #define PTRLEN_FROM_CONST_BYTES(a) make_ptrlen(a, sizeof(a))
 
@@ -211,6 +215,9 @@ bool smemeq(const void *av, const void *bv, size_t len);
  * been removed. */
 size_t encode_utf8(void *output, unsigned long ch);
 
+/* Write a string out in C string-literal format. */
+void write_c_string_literal(FILE *fp, ptrlen str);
+
 char *buildinfo(const char *newline);
 
 /*
@@ -415,4 +422,34 @@ static inline char *stripctrl_string(StripCtrlChars *sccpub, const char *str)
 #define pinitassert(P) const int __assert_dummy = 1/((int)(P))
 #endif
 
+/*
+ * A mechanism for loading a file from disk into a memory buffer where
+ * it can be picked apart as a BinarySource.
+ */
+struct LoadedFile {
+    char *data;
+    size_t len, max_size;
+    BinarySource_IMPLEMENTATION;
+};
+typedef enum {
+    LF_OK,      /* file loaded successfully */
+    LF_TOO_BIG, /* file didn't fit in buffer */
+    LF_ERROR,   /* error from stdio layer */
+} LoadFileStatus;
+LoadedFile *lf_new(size_t max_size);
+void lf_free(LoadedFile *lf);
+LoadFileStatus lf_load_fp(LoadedFile *lf, FILE *fp);
+LoadFileStatus lf_load(LoadedFile *lf, const Filename *filename);
+static inline ptrlen ptrlen_from_lf(LoadedFile *lf)
+{ return make_ptrlen(lf->data, lf->len); }
+
+/* Set the memory block of 'size' bytes at 'out' to the bitwise XOR of
+ * the two blocks of the same size at 'in1' and 'in2'.
+ *
+ * 'out' may point to exactly the same address as one of the inputs,
+ * but if the input and output blocks overlap in any other way, the
+ * result of this function is not guaranteed. No memmove-style effort
+ * is made to handle difficult overlap cases. */
+void memxor(uint8_t *out, const uint8_t *in1, const uint8_t *in2, size_t size);
+
 #endif

+ 333 - 111
source/putty/mpint.c

@@ -64,7 +64,7 @@ static uintmax_t shift_left_by_one_word(uintmax_t n)
         n << (shift_too_big ? 0 : BIGNUM_INT_BITS);
 }
 
-static mp_int *mp_make_sized(size_t nw)
+mp_int *mp_make_sized(size_t nw)
 {
     mp_int *x = snew_plus(mp_int, nw * sizeof(BignumInt));
     assert(nw);                   /* we outlaw the zero-word mp_int */
@@ -123,6 +123,14 @@ void mp_copy_into(mp_int *dest, mp_int *src)
     smemclr(dest->w + copy_nw, (dest->nw - copy_nw) * sizeof(BignumInt));
 }
 
+void mp_copy_integer_into(mp_int *r, uintmax_t n)
+{
+    for (size_t i = 0; i < r->nw; i++) {
+        r->w[i] = n;
+        n = shift_right_by_one_word(n);
+    }
+}
+
 /*
  * Conditional selection is done by negating 'which', to give a mask
  * word which is all 1s if which==1 and all 0s if which==0. Then you
@@ -227,7 +235,7 @@ mp_int *mp_from_decimal_pl(ptrlen decimal)
     mp_int *x = mp_make_sized(words);
     size_t i; // WINSCP
     for (i = 0; i < decimal.len; i++) {
-        mp_add_integer_into(x, x, ((char *)decimal.ptr)[i] - '0');
+        mp_add_integer_into(x, x, ((const char *)decimal.ptr)[i] - '0');
 
         if (i+1 == decimal.len)
             break;
@@ -258,7 +266,7 @@ mp_int *mp_from_hex_pl(ptrlen hex)
     mp_int *x = mp_make_sized(words);
     size_t nibble; // WINSCP
     for (nibble = 0; nibble < hex.len; nibble++) {
-        BignumInt digit = ((char *)hex.ptr)[hex.len-1 - nibble];
+        BignumInt digit = ((const char *)hex.ptr)[hex.len-1 - nibble];
 
         BignumInt lmask = ~-((BignumInt)((digit-'a')|('f'-digit))
                              >> (BIGNUM_INT_BITS-1));
@@ -964,7 +972,7 @@ unsigned mp_eq_integer(mp_int *x, uintmax_t n)
     return 1 ^ normalise_to_1(diff);   /* return 1 if diff _is_ zero */
 }
 
-void mp_neg_into(mp_int *r, mp_int *a)
+static void mp_neg_into(mp_int *r, mp_int *a)
 {
     mp_int zero;
     zero.nw = 0;
@@ -985,13 +993,6 @@ mp_int *mp_sub(mp_int *x, mp_int *y)
     return r;
 }
 
-mp_int *mp_neg(mp_int *a)
-{
-    mp_int *r = mp_make_sized(a->nw);
-    mp_neg_into(r, a);
-    return r;
-}
-
 /*
  * Internal routine: multiply and accumulate in the trivial O(N^2)
  * way. Sets r <- r + a*b.
@@ -1219,6 +1220,14 @@ void mp_rshift_fixed_into(mp_int *r, mp_int *a, size_t bits)
     }
 }
 
+mp_int *mp_lshift_fixed(mp_int *x, size_t bits)
+{
+    size_t words = (bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS;
+    mp_int *r = mp_make_sized(x->nw + words);
+    mp_lshift_fixed_into(r, x, bits);
+    return r;
+}
+
 mp_int *mp_rshift_fixed(mp_int *x, size_t bits)
 {
     size_t words = bits / BIGNUM_INT_BITS;
@@ -1236,19 +1245,17 @@ mp_int *mp_rshift_fixed(mp_int *x, size_t bits)
  * by a power of 2 words, using the usual bit twiddling to make the
  * whole shift conditional on the appropriate bit of n.
  */
-mp_int *mp_rshift_safe(mp_int *x, size_t bits)
+static void mp_rshift_safe_in_place(mp_int *r, size_t bits)
 {
     size_t wordshift = bits / BIGNUM_INT_BITS;
     size_t bitshift = bits % BIGNUM_INT_BITS;
 
-    mp_int *r = mp_copy(x);
-
     unsigned bit; // WINSCP
     unsigned clear = (r->nw - wordshift) >> (CHAR_BIT * sizeof(size_t) - 1);
     mp_cond_clear(r, clear);
 
     for (bit = 0; r->nw >> bit; bit++) {
-        size_t word_offset = 1 << bit;
+        size_t word_offset = (size_t)1 << bit;
         BignumInt mask = -(BignumInt)((wordshift >> bit) & 1);
         size_t i; // WINSCP
         for (i = 0; i < r->nw; i++) {
@@ -1270,10 +1277,60 @@ mp_int *mp_rshift_safe(mp_int *x, size_t bits)
             r->w[i] ^= (r->w[i] ^ w) & mask;
         }
     }
+}
 
+mp_int *mp_rshift_safe(mp_int *x, size_t bits)
+{
+    mp_int *r = mp_copy(x);
+    mp_rshift_safe_in_place(r, bits);
     return r;
 }
 
+void mp_rshift_safe_into(mp_int *r, mp_int *x, size_t bits)
+{
+    mp_copy_into(r, x);
+    mp_rshift_safe_in_place(r, bits);
+}
+
+static void mp_lshift_safe_in_place(mp_int *r, size_t bits)
+{
+    size_t wordshift = bits / BIGNUM_INT_BITS;
+    size_t bitshift = bits % BIGNUM_INT_BITS;
+
+    /*
+     * Same strategy as mp_rshift_safe_in_place, but of course the
+     * other way up.
+     */
+
+    unsigned clear = (r->nw - wordshift) >> (CHAR_BIT * sizeof(size_t) - 1);
+    mp_cond_clear(r, clear);
+
+    for (unsigned bit = 0; r->nw >> bit; bit++) {
+        size_t word_offset = (size_t)1 << bit;
+        BignumInt mask = -(BignumInt)((wordshift >> bit) & 1);
+        for (size_t i = r->nw; i-- > 0 ;) {
+            BignumInt w = mp_word(r, i - word_offset);
+            r->w[i] ^= (r->w[i] ^ w) & mask;
+        }
+    }
+
+    size_t downshift = BIGNUM_INT_BITS - bitshift;
+    size_t no_shift = (downshift >> BIGNUM_INT_BITS_BITS);
+    downshift &= ~-(size_t)no_shift;
+    BignumInt downshifted_mask = ~-(BignumInt)no_shift;
+
+    for (size_t i = r->nw; i-- > 0 ;) {
+        r->w[i] = (r->w[i] << bitshift) |
+            ((mp_word(r, i-1) >> downshift) & downshifted_mask);
+    }
+}
+
+void mp_lshift_safe_into(mp_int *r, mp_int *x, size_t bits)
+{
+    mp_copy_into(r, x);
+    mp_lshift_safe_in_place(r, bits);
+}
+
 void mp_reduce_mod_2to(mp_int *x, size_t p)
 {
     size_t word = p / BIGNUM_INT_BITS;
@@ -1638,10 +1695,10 @@ mp_int *mp_modpow(mp_int *base, mp_int *exponent, mp_int *modulus)
 }
 
 /*
- * Given two coprime nonzero input integers a,b, returns two integers
- * A,B such that A*a - B*b = 1. A,B will be the minimal non-negative
- * pair satisfying that criterion, which is equivalent to saying that
- * 0<=A<b and 0<=B<a.
+ * Given two input integers a,b which are not both even, computes d =
+ * gcd(a,b) and also two integers A,B such that A*a - B*b = d. A,B
+ * will be the minimal non-negative pair satisfying that criterion,
+ * which is equivalent to saying that 0 <= A < b/d and 0 <= B < a/d.
  *
  * This algorithm is an adapted form of Stein's algorithm, which
  * computes gcd(a,b) using only addition and bit shifts (i.e. without
@@ -1653,9 +1710,11 @@ mp_int *mp_modpow(mp_int *base, mp_int *exponent, mp_int *modulus)
  *  - if both of a,b are odd, then WLOG a>b, and gcd(a,b) =
  *    gcd(b,(a-b)/2).
  *
- * For this application, I always expect the actual gcd to be coprime,
- * so we can rule out the 'both even' initial case. So this function
- * just performs a sequence of reductions in the following form:
+ * Sometimes this function is used for modular inversion, in which
+ * case we already know we expect the two inputs to be coprime, so to
+ * save time the 'both even' initial case is assumed not to arise (or
+ * to have been handled already by the caller). So this function just
+ * performs a sequence of reductions in the following form:
  *
  *  - if a,b are both odd, sort them so that a > b, and replace a with
  *    b-a; otherwise sort them so that a is the even one
@@ -1666,14 +1725,14 @@ mp_int *mp_modpow(mp_int *base, mp_int *exponent, mp_int *modulus)
  * generate those in each case, based on the coefficients from the
  * reduced pair of numbers:
  *
- *  - If a is even, and u,v are such that u*(a/2) + v*b = 1:
- *     + if u is also even, then this is just (u/2)*a + v*b = 1
- *     + otherwise, (u+b)*(a/2) + (v-a/2)*b is also equal to 1, and
+ *  - If a is even, and u,v are such that u*(a/2) + v*b = d:
+ *     + if u is also even, then this is just (u/2)*a + v*b = d
+ *     + otherwise, (u+b)*(a/2) + (v-a/2)*b is also equal to d, and
  *       since u and b are both odd, (u+b)/2 is an integer, so we have
- *       ((u+b)/2)*a + (v-a/2)*b = 1.
+ *       ((u+b)/2)*a + (v-a/2)*b = d.
  *
- *  - If a,b are both odd, and u,v are such that u*b + v*(a-b) = 1,
- *    then v*a + (u-v)*b = 1.
+ *  - If a,b are both odd, and u,v are such that u*b + v*(a-b) = d,
+ *    then v*a + (u-v)*b = d.
  *
  * In the case where we passed from (a,b) to (b,(a-b)/2), we regard it
  * as having first subtracted b from a and then halved a, so both of
@@ -1691,11 +1750,11 @@ mp_int *mp_modpow(mp_int *base, mp_int *exponent, mp_int *modulus)
  * Also, since these mp_ints are generally treated as unsigned, we
  * store the coefficients by absolute value, with the semantics that
  * they always have opposite sign, and in the unwinding loop we keep a
- * bit indicating whether Aa-Bb is currently expected to be +1 or -1,
- * so that we can do one final conditional adjustment if it's -1.
+ * bit indicating whether Aa-Bb is currently expected to be +d or -d,
+ * so that we can do one final conditional adjustment if it's -d.
  *
  * Once the reduction rules have managed to reduce the input numbers
- * to (0,1), then they are stable (the next reduction will always
+ * to (0,d), then they are stable (the next reduction will always
  * divide the even one by 2, which maps 0 to 0). So it doesn't matter
  * if we do more steps of the algorithm than necessary; hence, for
  * constant time, we just need to find the maximum number we could
@@ -1714,7 +1773,7 @@ mp_int *mp_modpow(mp_int *base, mp_int *exponent, mp_int *modulus)
  * n further steps each of which subtracts 1 from y and halves it.
  */
 static void mp_bezout_into(mp_int *a_coeff_out, mp_int *b_coeff_out,
-                           mp_int *a_in, mp_int *b_in)
+                           mp_int *gcd_out, mp_int *a_in, mp_int *b_in)
 {
     size_t nw = size_t_max(1, size_t_max(a_in->nw, b_in->nw));
 
@@ -1775,100 +1834,126 @@ static void mp_bezout_into(mp_int *a_coeff_out, mp_int *b_coeff_out,
     }
 
     /*
-     * Now we expect to have reduced the two numbers to 0 and 1,
+     * Now we expect to have reduced the two numbers to 0 and d,
      * although we don't know which way round. (But we avoid checking
      * this by assertion; sometimes we'll need to do this computation
      * without giving away that we already know the inputs were bogus.
      * So we'd prefer to just press on and return nonsense.)
      */
 
-    /*
-     * So their Bezout coefficients at this point are simply
-     * themselves.
-     */
-    mp_copy_into(ac, a);
-    mp_copy_into(bc, b);
+    if (gcd_out) {
+        /*
+         * At this point we can return the actual gcd. Since one of
+         * a,b is it and the other is zero, the easiest way to get it
+         * is to add them together.
+         */
+        mp_add_into(gcd_out, a, b);
+    }
 
     /*
-     * We'll maintain the invariant as we unwind that ac * a - bc * b
-     * is either +1 or -1, and we'll remember which. (We _could_ keep
-     * it at +1 the whole time, but it would cost more work every time
-     * round the loop, so it's cheaper to fix that up once at the
-     * end.)
-     *
-     * Initially, the result is +1 if a was the nonzero value after
-     * reduction, and -1 if b was.
+     * If the caller _only_ wanted the gcd, and neither Bezout
+     * coefficient is even required, we can skip the entire unwind
+     * stage.
      */
-    { // WINSCP
-    unsigned minus_one = b->w[0];
+    if (a_coeff_out || b_coeff_out) {
 
-    for (step = steps; step-- > 0 ;) {
         /*
-         * Recover the data from the step we're unwinding.
+         * The Bezout coefficients of a,b at this point are simply 0
+         * for whichever of a,b is zero, and 1 for whichever is
+         * nonzero. The nonzero number equals gcd(a,b), which by
+         * assumption is odd, so we can do this by just taking the low
+         * bit of each one.
          */
-        unsigned both_odd = mp_get_bit(record, step*2);
-        unsigned swap = mp_get_bit(record, step*2+1);
+        ac->w[0] = mp_get_bit(a, 0);
+        bc->w[0] = mp_get_bit(b, 0);
 
         /*
-         * Unwind the division: if our coefficient of a is odd, we
-         * adjust the coefficients by +b and +a respectively.
+         * Overwrite a,b themselves with those same numbers. This has
+         * the effect of dividing both of them by d, which will
+         * arrange that during the unwind stage we generate the
+         * minimal coefficients instead of a larger pair.
          */
-        unsigned adjust = ac->w[0] & 1;
-        mp_cond_add_into(ac, ac, b, adjust);
-        mp_cond_add_into(bc, bc, a, adjust);
+        mp_copy_into(a, ac);
+        mp_copy_into(b, bc);
 
         /*
-         * Now ac is definitely even, so we divide it by two.
+         * We'll maintain the invariant as we unwind that ac * a - bc
+         * * b is either +d or -d (or rather, +1/-1 after scaling by
+         * d), and we'll remember which. (We _could_ keep it at +d the
+         * whole time, but it would cost more work every time round
+         * the loop, so it's cheaper to fix that up once at the end.)
+         *
+         * Initially, the result is +d if a was the nonzero value after
+         * reduction, and -d if b was.
          */
-        mp_rshift_fixed_into(ac, ac, 1);
+        unsigned minus_d = b->w[0];
 
-        /*
-         * Now unwind the subtraction, if there was one, by adding
-         * ac to bc.
-         */
-        mp_cond_add_into(bc, bc, ac, both_odd);
+        for (size_t step = steps; step-- > 0 ;) {
+            /*
+             * Recover the data from the step we're unwinding.
+             */
+            unsigned both_odd = mp_get_bit(record, step*2);
+            unsigned swap = mp_get_bit(record, step*2+1);
+
+            /*
+             * Unwind the division: if our coefficient of a is odd, we
+             * adjust the coefficients by +b and +a respectively.
+             */
+            unsigned adjust = ac->w[0] & 1;
+            mp_cond_add_into(ac, ac, b, adjust);
+            mp_cond_add_into(bc, bc, a, adjust);
+
+            /*
+             * Now ac is definitely even, so we divide it by two.
+             */
+            mp_rshift_fixed_into(ac, ac, 1);
+
+            /*
+             * Now unwind the subtraction, if there was one, by adding
+             * ac to bc.
+             */
+            mp_cond_add_into(bc, bc, ac, both_odd);
+
+            /*
+             * Undo the transformation of the input numbers, by
+             * multiplying a by 2 and then adding b to a (the latter
+             * only if both_odd).
+             */
+            mp_lshift_fixed_into(a, a, 1);
+            mp_cond_add_into(a, a, b, both_odd);
+
+            /*
+             * Finally, undo the swap. If we do swap, this also
+             * reverses the sign of the current result ac*a+bc*b.
+             */
+            mp_cond_swap(a, b, swap);
+            mp_cond_swap(ac, bc, swap);
+            minus_d ^= swap;
+        }
 
         /*
-         * Undo the transformation of the input numbers, by
-         * multiplying a by 2 and then adding b to a (the latter
-         * only if both_odd).
+         * Now we expect to have recovered the input a,b (or rather,
+         * the versions of them divided by d). But we might find that
+         * our current result is -d instead of +d, that is, we have
+         * A',B' such that A'a - B'b = -d.
+         *
+         * In that situation, we set A = b-A' and B = a-B', giving us
+         * Aa-Bb = ab - A'a - ab + B'b = +1.
          */
-        mp_lshift_fixed_into(a, a, 1);
-        mp_cond_add_into(a, a, b, both_odd);
+        mp_sub_into(tmp, b, ac);
+        mp_select_into(ac, ac, tmp, minus_d);
+        mp_sub_into(tmp, a, bc);
+        mp_select_into(bc, bc, tmp, minus_d);
 
         /*
-         * Finally, undo the swap. If we do swap, this also
-         * reverses the sign of the current result ac*a+bc*b.
+         * Now we really are done. Return the outputs.
          */
-        mp_cond_swap(a, b, swap);
-        mp_cond_swap(ac, bc, swap);
-        minus_one ^= swap;
-    }
-
-    /*
-     * Now we expect to have recovered the input a,b.
-     */
-    assert(mp_cmp_eq(a, a_in) & mp_cmp_eq(b, b_in));
-
-    /*
-     * But we might find that our current result is -1 instead of +1,
-     * that is, we have A',B' such that A'a - B'b = -1.
-     *
-     * In that situation, we set A = b-A' and B = a-B', giving us
-     * Aa-Bb = ab - A'a - ab + B'b = +1.
-     */
-    mp_sub_into(tmp, b, ac);
-    mp_select_into(ac, ac, tmp, minus_one);
-    mp_sub_into(tmp, a, bc);
-    mp_select_into(bc, bc, tmp, minus_one);
+        if (a_coeff_out)
+            mp_copy_into(a_coeff_out, ac);
+        if (b_coeff_out)
+            mp_copy_into(b_coeff_out, bc);
 
-    /*
-     * Now we really are done. Return the outputs.
-     */
-    if (a_coeff_out)
-        mp_copy_into(a_coeff_out, ac);
-    if (b_coeff_out)
-        mp_copy_into(b_coeff_out, bc);
+    }
 
     mp_free(a);
     mp_free(b);
@@ -1877,16 +1962,70 @@ static void mp_bezout_into(mp_int *a_coeff_out, mp_int *b_coeff_out,
     mp_free(tmp);
     mp_free(record);
     } // WINSCP
-    } // WINSCP
 }
 
 mp_int *mp_invert(mp_int *x, mp_int *m)
 {
     mp_int *result = mp_make_sized(m->nw);
-    mp_bezout_into(result, NULL, x, m);
+    mp_bezout_into(result, NULL, NULL, x, m);
     return result;
 }
 
+void mp_gcd_into(mp_int *a, mp_int *b, mp_int *gcd, mp_int *A, mp_int *B)
+{
+    /*
+     * Identify shared factors of 2. To do this we OR the two numbers
+     * to get something whose lowest set bit is in the right place,
+     * remove all higher bits by ANDing it with its own negation, and
+     * use mp_get_nbits to find the location of the single remaining
+     * set bit.
+     */
+    mp_int *tmp = mp_make_sized(size_t_max(a->nw, b->nw));
+    for (size_t i = 0; i < tmp->nw; i++)
+        tmp->w[i] = mp_word(a, i) | mp_word(b, i);
+    BignumCarry carry = 1;
+    for (size_t i = 0; i < tmp->nw; i++) {
+        BignumInt negw;
+        BignumADC(negw, carry, 0, ~tmp->w[i], carry);
+        tmp->w[i] &= negw;
+    }
+    size_t shift = mp_get_nbits(tmp) - 1;
+    mp_free(tmp);
+
+    /*
+     * Make copies of a,b with those shared factors of 2 divided off,
+     * so that at least one is odd (which is the precondition for
+     * mp_bezout_into). Compute the gcd of those.
+     */
+    mp_int *as = mp_rshift_safe(a, shift);
+    mp_int *bs = mp_rshift_safe(b, shift);
+    mp_bezout_into(A, B, gcd, as, bs);
+    mp_free(as);
+    mp_free(bs);
+
+    /*
+     * And finally shift the gcd back up (unless the caller didn't
+     * even ask for it), to put the shared factors of 2 back in.
+     */
+    if (gcd)
+        mp_lshift_safe_in_place(gcd, shift);
+}
+
+mp_int *mp_gcd(mp_int *a, mp_int *b)
+{
+    mp_int *gcd = mp_make_sized(size_t_min(a->nw, b->nw));
+    mp_gcd_into(a, b, gcd, NULL, NULL);
+    return gcd;
+}
+
+unsigned mp_coprime(mp_int *a, mp_int *b)
+{
+    mp_int *gcd = mp_gcd(a, b);
+    unsigned toret = mp_eq_integer(gcd, 1);
+    mp_free(gcd);
+    return toret;
+}
+
 static uint32_t recip_approx_32(uint32_t x)
 {
     /*
@@ -2013,7 +2152,7 @@ void mp_divmod_into(mp_int *n, mp_int *d, mp_int *q_out, mp_int *r_out)
     size_t shift_up = 0;
     size_t i; // WINSCP
     for (i = BIGNUM_INT_BITS_BITS; i-- > 0;) {
-        size_t sl = 1 << i;               /* left shift count */
+        size_t sl = (size_t)1 << i;       /* left shift count */
         size_t sr = 64 - sl;     /* complementary right-shift count */
 
         /* Should we shift up? */
@@ -2050,7 +2189,7 @@ void mp_divmod_into(mp_int *n, mp_int *d, mp_int *q_out, mp_int *r_out)
      * instructions, e.g. by splitting up into cases.
      */
     for (i = BIGNUM_INT_BITS_BITS; i-- > 0;) {
-        size_t sl = 1 << i;               /* left shift count */
+        size_t sl = (size_t)1 << i;       /* left shift count */
         size_t sr = 64 - sl;     /* complementary right-shift count */
 
         /* Should we shift up? */
@@ -2248,6 +2387,82 @@ mp_int *mp_mod(mp_int *n, mp_int *d)
     return r;
 }
 
+mp_int *mp_nthroot(mp_int *y, unsigned n, mp_int *remainder_out)
+{
+    /*
+     * Allocate scratch space.
+     */
+    mp_int **alloc, **powers, **newpowers, *scratch;
+    size_t nalloc = 2*(n+1)+1;
+    alloc = snewn(nalloc, mp_int *);
+    for (size_t i = 0; i < nalloc; i++)
+        alloc[i] = mp_make_sized(y->nw + 1);
+    powers = alloc;
+    newpowers = alloc + (n+1);
+    scratch = alloc[2*n+2];
+
+    /*
+     * We're computing the rounded-down nth root of y, i.e. the
+     * maximal x such that x^n <= y. We try to add 2^i to it for each
+     * possible value of i, starting from the largest one that might
+     * fit (i.e. such that 2^{n*i} fits in the size of y) downwards to
+     * i=0.
+     *
+     * We track all the smaller powers of x in the array 'powers'. In
+     * each iteration, if we update x, we update all of those values
+     * to match.
+     */
+    mp_copy_integer_into(powers[0], 1);
+    for (size_t s = mp_max_bits(y) / n + 1; s-- > 0 ;) {
+        /*
+         * Let b = 2^s. We need to compute the powers (x+b)^i for each
+         * i, starting from our recorded values of x^i.
+         */
+        for (size_t i = 0; i < n+1; i++) {
+            /*
+             * (x+b)^i = x^i
+             *         + (i choose 1) x^{i-1} b
+             *         + (i choose 2) x^{i-2} b^2
+             *         + ...
+             *         + b^i
+             */
+            uint16_t binom = 1;       /* coefficient of b^i */
+            mp_copy_into(newpowers[i], powers[i]);
+            for (size_t j = 0; j < i; j++) {
+                /* newpowers[i] += binom * powers[j] * 2^{(i-j)*s} */
+                mp_mul_integer_into(scratch, powers[j], binom);
+                mp_lshift_fixed_into(scratch, scratch, (i-j) * s);
+                mp_add_into(newpowers[i], newpowers[i], scratch);
+
+                uint32_t binom_mul = binom;
+                binom_mul *= (i-j);
+                binom_mul /= (j+1);
+                assert(binom_mul < 0x10000);
+                binom = binom_mul;
+            }
+        }
+
+        /*
+         * Now, is the new value of x^n still <= y? If so, update.
+         */
+        unsigned newbit = mp_cmp_hs(y, newpowers[n]);
+        for (size_t i = 0; i < n+1; i++)
+            mp_select_into(powers[i], powers[i], newpowers[i], newbit);
+    }
+
+    if (remainder_out)
+        mp_sub_into(remainder_out, y, powers[n]);
+
+    mp_int *root = mp_new(mp_max_bits(y) / n);
+    mp_copy_into(root, powers[1]);
+
+    for (size_t i = 0; i < nalloc; i++)
+        mp_free(alloc[i]);
+    sfree(alloc);
+
+    return root;
+}
+
 mp_int *mp_modmul(mp_int *x, mp_int *y, mp_int *modulus)
 {
     mp_int *product = mp_mul(x, y);
@@ -2548,10 +2763,8 @@ mp_int *mp_random_bits_fn(size_t bits, random_read_fn_t random_read)
     } // WINSCP
 }
 
-mp_int *mp_random_in_range_fn(mp_int *lo, mp_int *hi, random_read_fn_t rf)
+mp_int *mp_random_upto_fn(mp_int *limit, random_read_fn_t rf)
 {
-    mp_int *n_outcomes = mp_sub(hi, lo);
-
     /*
      * It would be nice to generate our random numbers in such a way
      * as to make every possible outcome literally equiprobable. But
@@ -2561,10 +2774,19 @@ mp_int *mp_random_in_range_fn(mp_int *lo, mp_int *hi, random_read_fn_t rf)
      * is acceptable on the grounds that you'd have to examine so many
      * outputs to even detect it.
      */
-    mp_int *unreduced = mp_random_bits_fn(mp_max_bits(n_outcomes) + 128, rf);
-    mp_int *reduced = mp_mod(unreduced, n_outcomes);
-    mp_add_into(reduced, reduced, lo);
+    mp_int *unreduced = mp_random_bits_fn(mp_max_bits(limit) + 128, rf);
+    mp_int *reduced = mp_mod(unreduced, limit);
     mp_free(unreduced);
-    mp_free(n_outcomes);
     return reduced;
 }
+
+mp_int *mp_random_in_range_fn(mp_int *lo, mp_int *hi, random_read_fn_t rf)
+{
+    mp_int *n_outcomes = mp_sub(hi, lo);
+    mp_int *addend = mp_random_upto_fn(n_outcomes, rf);
+    mp_int *result = mp_make_sized(hi->nw);
+    mp_add_into(result, addend, lo);
+    mp_free(addend);
+    mp_free(n_outcomes);
+    return result;
+}

+ 44 - 2
source/putty/mpint.h

@@ -187,9 +187,10 @@ mp_int *mp_max(mp_int *x, mp_int *y);
 void mp_dump(FILE *fp, const char *prefix, mp_int *x, const char *suffix);
 
 /*
- * Overwrite one mp_int with another.
+ * Overwrite one mp_int with another, or with a plain integer.
  */
 void mp_copy_into(mp_int *dest, mp_int *src);
+void mp_copy_integer_into(mp_int *dest, uintmax_t n);
 
 /*
  * Conditional selection. Overwrites dest with either src0 or src1,
@@ -267,6 +268,17 @@ void mp_divmod_into(mp_int *n, mp_int *d, mp_int *q, mp_int *r);
 mp_int *mp_div(mp_int *n, mp_int *d);
 mp_int *mp_mod(mp_int *x, mp_int *modulus);
 
+/*
+ * Integer nth root. mp_nthroot returns the largest integer x such
+ * that x^n <= y, and if 'remainder' is non-NULL then it fills it with
+ * the residue (y - x^n).
+ *
+ * Currently, n has to be small enough that the largest binomial
+ * coefficient (n choose k) fits in 16 bits, which works out to at
+ * most 18.
+ */
+mp_int *mp_nthroot(mp_int *y, unsigned n, mp_int *remainder);
+
 /*
  * Trivially easy special case of mp_mod: reduce a number mod a power
  * of two.
@@ -281,6 +293,25 @@ void mp_reduce_mod_2to(mp_int *x, size_t p);
 mp_int *mp_invert_mod_2to(mp_int *x, size_t p);
 mp_int *mp_invert(mp_int *x, mp_int *modulus);
 
+/*
+ * Greatest common divisor.
+ *
+ * mp_gcd_into also returns a pair of Bezout coefficients, namely A,B
+ * such that a*A - b*B = gcd. (The minus sign is so that both returned
+ * coefficients can be positive.)
+ *
+ * You can pass any of mp_gcd_into's output pointers as NULL if you
+ * don't need that output value.
+ *
+ * mp_gcd is a wrapper with a less cumbersome API, for the case where
+ * the only output value you need is the gcd itself. mp_coprime is
+ * even easier, if all you care about is whether or not that gcd is 1.
+ */
+mp_int *mp_gcd(mp_int *a, mp_int *b);
+void mp_gcd_into(mp_int *a, mp_int *b,
+                 mp_int *gcd_out, mp_int *A_out, mp_int *B_out);
+unsigned mp_coprime(mp_int *a, mp_int *b);
+
 /*
  * System for taking square roots modulo an odd prime.
  *
@@ -371,10 +402,17 @@ mp_int *mp_modadd(mp_int *x, mp_int *y, mp_int *modulus);
 mp_int *mp_modsub(mp_int *x, mp_int *y, mp_int *modulus);
 
 /*
- * Shift an mp_int right by a given number of bits. The shift count is
+ * Shift an mp_int by a given number of bits. The shift count is
  * considered to be secret data, and as a result, the algorithm takes
  * O(n log n) time instead of the obvious O(n).
+ *
+ * There's no mp_lshift_safe, because the size of mp_int to allocate
+ * would not be able to avoid depending on the shift count. So if you
+ * need to behave independently of the size of a left shift, you have
+ * to know a bound on the space you'll need by some other means.
  */
+void mp_lshift_safe_into(mp_int *r, mp_int *x, size_t shift);
+void mp_rshift_safe_into(mp_int *r, mp_int *x, size_t shift);
 mp_int *mp_rshift_safe(mp_int *x, size_t shift);
 
 /*
@@ -387,6 +425,7 @@ mp_int *mp_rshift_safe(mp_int *x, size_t shift);
  */
 void mp_lshift_fixed_into(mp_int *r, mp_int *a, size_t shift);
 void mp_rshift_fixed_into(mp_int *r, mp_int *x, size_t shift);
+mp_int *mp_lshift_fixed(mp_int *x, size_t shift);
 mp_int *mp_rshift_fixed(mp_int *x, size_t shift);
 
 /*
@@ -402,13 +441,16 @@ mp_int *mp_rshift_fixed(mp_int *x, size_t shift);
  * then _they_ have link-time dependencies on both modules.)
  *
  * mp_random_bits[_fn] returns an integer 0 <= n < 2^bits.
+ * mp_random_upto[_fn](limit) returns an integer 0 <= n < limit.
  * mp_random_in_range[_fn](lo,hi) returns an integer lo <= n < hi.
  */
 typedef void (*random_read_fn_t)(void *, size_t);
 mp_int *mp_random_bits_fn(size_t bits, random_read_fn_t randfn);
+mp_int *mp_random_upto_fn(mp_int *limit, random_read_fn_t randfn);
 mp_int *mp_random_in_range_fn(
     mp_int *lo_inclusive, mp_int *hi_exclusive, random_read_fn_t randfn);
 #define mp_random_bits(bits) mp_random_bits_fn(bits, random_read)
+#define mp_random_upto(limit) mp_random_upto_fn(limit, random_read)
 #define mp_random_in_range(lo, hi) mp_random_in_range_fn(lo, hi, random_read)
 
 #endif /* PUTTY_MPINT_H */

+ 3 - 0
source/putty/mpint_i.h

@@ -319,3 +319,6 @@ struct MontyContext {
      */
     mp_int *scratch;
 };
+
+/* Functions shared between mpint.c and mpunsafe.c */
+mp_int *mp_make_sized(size_t nw);

+ 29 - 13
source/putty/network.h

@@ -44,25 +44,37 @@ struct Plug {
     const struct PlugVtable *vt;
 };
 
+typedef enum PlugLogType {
+    PLUGLOG_CONNECT_TRYING,
+    PLUGLOG_CONNECT_FAILED,
+    PLUGLOG_CONNECT_SUCCESS,
+    PLUGLOG_PROXY_MSG,
+} PlugLogType;
+
 struct PlugVtable {
-    void (*log)(Plug *p, int type, SockAddr *addr, int port,
+    void (*log)(Plug *p, PlugLogType type, SockAddr *addr, int port,
                 const char *error_msg, int error_code);
     /*
      * Passes the client progress reports on the process of setting
      * up the connection.
      *
-     *  - type==0 means we are about to try to connect to address
-     *    `addr' (error_msg and error_code are ignored)
-     *  - type==1 means we have failed to connect to address `addr'
-     *    (error_msg and error_code are supplied). This is not a
-     *    fatal error - we may well have other candidate addresses
-     *    to fall back to. When it _is_ fatal, the closing()
+     *  - PLUGLOG_CONNECT_TRYING means we are about to try to connect
+     *    to address `addr' (error_msg and error_code are ignored)
+     *
+     *  - PLUGLOG_CONNECT_FAILED means we have failed to connect to
+     *    address `addr' (error_msg and error_code are supplied). This
+     *    is not a fatal error - we may well have other candidate
+     *    addresses to fall back to. When it _is_ fatal, the closing()
      *    function will be called.
-     *  - type==2 means that error_msg contains a line of generic
-     *    logging information about setting up the connection. This
-     *    will typically be a wodge of standard-error output from a
-     *    proxy command, so the receiver should probably prefix it to
-     *    indicate this.
+     *
+     *  - PLUGLOG_CONNECT_SUCCESS means we have succeeded in
+     *    connecting to address `addr'.
+     *
+     *  - PLUGLOG_PROXY_MSG means that error_msg contains a line of
+     *    logging information from whatever the connection is being
+     *    proxied through. This will typically be a wodge of
+     *    standard-error output from a local proxy command, so the
+     *    receiver should probably prefix it to indicate this.
      */
     void (*closing)
      (Plug *p, const char *error_msg, int error_code, bool calling_back);
@@ -275,9 +287,13 @@ char *get_hostname(void);
 /*
  * Trivial socket implementation which just stores an error. Found in
  * errsock.c.
+ *
+ * The consume_string variant takes an already-formatted dynamically
+ * allocated string, and takes over ownership of that string.
  */
 Socket *new_error_socket_fmt(Plug *plug, const char *fmt, ...)
     PRINTF_LIKE(2, 3);
+Socket *new_error_socket_consume_string(Plug *plug, char *errmsg);
 
 /*
  * Trivial plug that does absolutely nothing. Found in nullplug.c.
@@ -294,7 +310,7 @@ extern Plug *const nullplug;
  * Exports from be_misc.c.
  */
 void backend_socket_log(Seat *seat, LogContext *logctx,
-                        int type, SockAddr *addr, int port,
+                        PlugLogType type, SockAddr *addr, int port,
                         const char *error_msg, int error_code, Conf *conf,
                         bool session_started);
 

+ 6 - 7
source/putty/nullplug.c

@@ -7,8 +7,8 @@
 
 #include "putty.h"
 
-static void nullplug_socket_log(Plug *plug, int type, SockAddr *addr, int port,
-                                const char *error_msg, int error_code)
+static void nullplug_socket_log(Plug *plug, PlugLogType type, SockAddr *addr,
+                                int port, const char *err_msg, int err_code)
 {
 }
 
@@ -27,11 +27,10 @@ static void nullplug_sent(Plug *plug, size_t bufsize)
 }
 
 static const PlugVtable nullplug_plugvt = {
-    nullplug_socket_log,
-    nullplug_closing,
-    nullplug_receive,
-    nullplug_sent,
-    NULL
+    .log = nullplug_socket_log,
+    .closing = nullplug_closing,
+    .receive = nullplug_receive,
+    .sent = nullplug_sent,
 };
 
 static Plug nullplug_plug = { &nullplug_plugvt };

+ 150 - 39
source/putty/pageant.h

@@ -11,35 +11,98 @@
  */
 #define AGENT_MAX_MSGLEN  262144
 
-typedef void (*pageant_logfn_t)(void *logctx, const char *fmt, va_list ap);
+typedef struct PageantClientVtable PageantClientVtable;
+typedef struct PageantClient PageantClient;
+typedef struct PageantClientInfo PageantClientInfo;
+typedef struct PageantClientRequestId PageantClientRequestId;
+typedef struct PageantClientDialogId PageantClientDialogId;
+struct PageantClient {
+    const struct PageantClientVtable *vt;
+    PageantClientInfo *info;    /* used by the central Pageant code */
+
+    /* Setting this flag prevents the 'log' vtable entry from ever
+     * being called, so that it's safe to make it NULL. This also
+     * allows optimisations in the core code (it can avoid entire
+     * loops that are only used for logging purposes). So you can also
+     * set it dynamically if you find out at run time that you're not
+     * doing logging. */
+    bool suppress_logging;
+};
+struct PageantClientVtable {
+    void (*log)(PageantClient *pc, PageantClientRequestId *reqid,
+                const char *fmt, va_list ap);
+    void (*got_response)(PageantClient *pc, PageantClientRequestId *reqid,
+                         ptrlen response);
+    bool (*ask_passphrase)(PageantClient *pc, PageantClientDialogId *dlgid,
+                           const char *key_comment);
+};
+
+static inline void pageant_client_log_v(
+    PageantClient *pc, PageantClientRequestId *reqid,
+    const char *fmt, va_list ap)
+{
+    if (!pc->suppress_logging)
+        pc->vt->log(pc, reqid, fmt, ap);
+}
+static inline PRINTF_LIKE(3, 4) void pageant_client_log(
+    PageantClient *pc, PageantClientRequestId *reqid, const char *fmt, ...)
+{
+    if (!pc->suppress_logging) {
+        va_list ap;
+        va_start(ap, fmt);
+        pc->vt->log(pc, reqid, fmt, ap);
+        va_end(ap);
+    }
+}
+static inline void pageant_client_got_response(
+    PageantClient *pc, PageantClientRequestId *reqid, ptrlen response)
+{ pc->vt->got_response(pc, reqid, response); }
+static inline bool pageant_client_ask_passphrase(
+    PageantClient *pc, PageantClientDialogId *dlgid, const char *comment)
+{ return pc->vt->ask_passphrase(pc, dlgid, comment); }
+
+/* PageantClientRequestId is used to match up responses to the agent
+ * requests they refer to. A client may allocate one of these for each
+ * call to pageant_handle_request, (probably as a subfield of some
+ * larger struct on the client side) and expect the same pointer to be
+ * passed back in pageant_client_got_response. */
+struct PageantClientRequestId { int unused_; };
 
 /*
  * Initial setup.
  */
 void pageant_init(void);
 
+/*
+ * Register and unregister PageantClients. This is necessary so that
+ * when a PageantClient goes away, any unfinished asynchronous
+ * requests can be cleaned up.
+ *
+ * pageant_register_client will fill in pc->id. The client itself
+ * should not touch that field.
+ */
+void pageant_register_client(PageantClient *pc);
+void pageant_unregister_client(PageantClient *pc);
+
 /*
  * The main agent function that answers messages.
  *
  * Expects a message/length pair as input, minus its initial length
  * field but still with its type code on the front.
  *
- * Returns a fully formatted message as output, *with* its initial
- * length field, and sets *outlen to the full size of that message.
+ * When a response is ready, the got_response method in the
+ * PageantClient vtable will be passed it in the form of a ptrlen,
+ * again minus its length field.
  */
-void pageant_handle_msg(BinarySink *bs,
-                        const void *msg, int msglen,
-                        void *logctx, pageant_logfn_t logfn);
+void pageant_handle_msg(PageantClient *pc, PageantClientRequestId *reqid,
+                        ptrlen msg);
 
 /*
- * Construct a failure response. Useful for agent front ends which
- * suffer a problem before they even get to pageant_handle_msg.
- *
- * 'log_reason' is only used if logfn is not NULL.
+ * Send the core Pageant code a response to a passphrase request.
  */
-void pageant_failure_msg(BinarySink *bs,
-                         const char *log_reason,
-                         void *logctx, pageant_logfn_t logfn);
+void pageant_passphrase_request_success(PageantClientDialogId *dlgid,
+                                        ptrlen passphrase);
+void pageant_passphrase_request_refused(PageantClientDialogId *dlgid);
 
 /*
  * Construct a list of public keys, just as the two LIST_IDENTITIES
@@ -49,21 +112,17 @@ void pageant_make_keylist1(BinarySink *);
 void pageant_make_keylist2(BinarySink *);
 
 /*
- * Accessor functions for Pageant's internal key lists. Fetch the nth
- * key; count the keys; attempt to add a key (returning true on
- * success, in which case the ownership of the key structure has been
- * taken over by pageant.c); attempt to delete a key (returning true
- * on success, in which case the ownership of the key structure is
- * passed back to the client).
+ * Accessor functions for Pageant's internal key lists, used by GUI
+ * Pageant, to count the keys, to delete a key, or to re-encrypt a
+ * decrypted-on-demand key (SSH-2 only).
  */
-RSAKey *pageant_nth_ssh1_key(int i);
-ssh2_userkey *pageant_nth_ssh2_key(int i);
 int pageant_count_ssh1_keys(void);
 int pageant_count_ssh2_keys(void);
-bool pageant_add_ssh1_key(RSAKey *rkey);
-bool pageant_add_ssh2_key(ssh2_userkey *skey);
-bool pageant_delete_ssh1_key(RSAKey *rkey);
-bool pageant_delete_ssh2_key(ssh2_userkey *skey);
+bool pageant_delete_nth_ssh1_key(int i);
+bool pageant_delete_nth_ssh2_key(int i);
+bool pageant_reencrypt_nth_ssh2_key(int i);
+void pageant_delete_all(void);
+void pageant_reencrypt_all(void);
 
 /*
  * This callback must be provided by the Pageant front end code.
@@ -83,11 +142,48 @@ void keylist_update(void);
  * socket pointer. Also, provide a logging function later if you want
  * to.
  */
+typedef struct PageantListenerClientVtable PageantListenerClientVtable;
+typedef struct PageantListenerClient PageantListenerClient;
+struct PageantListenerClient {
+    const PageantListenerClientVtable *vt;
+    /* suppress_logging flag works similarly to the one in
+     * PageantClient, but it is only read when a new connection comes
+     * in. So if you do need to change it in mid-run, expect existing
+     * agent connections to still use the old value. */
+    bool suppress_logging;
+};
+struct PageantListenerClientVtable {
+    void (*log)(PageantListenerClient *, const char *fmt, va_list ap);
+    bool (*ask_passphrase)(PageantListenerClient *pc,
+                           PageantClientDialogId *dlgid,
+                           const char *key_comment);
+};
+
+static inline void pageant_listener_client_log_v(
+    PageantListenerClient *plc, const char *fmt, va_list ap)
+{
+    if (!plc->suppress_logging)
+        plc->vt->log(plc, fmt, ap);
+}
+static inline PRINTF_LIKE(2, 3) void pageant_listener_client_log(
+    PageantListenerClient *plc, const char *fmt, ...)
+{
+    if (!plc->suppress_logging) {
+        va_list ap;
+        va_start(ap, fmt);
+        plc->vt->log(plc, fmt, ap);
+        va_end(ap);
+    }
+}
+static inline bool pageant_listener_client_ask_passphrase(
+    PageantListenerClient *plc, PageantClientDialogId *dlgid,
+    const char *comment)
+{ return plc->vt->ask_passphrase(plc, dlgid, comment); }
+
 struct pageant_listen_state;
-struct pageant_listen_state *pageant_listener_new(Plug **plug);
+struct pageant_listen_state *pageant_listener_new(
+    Plug **plug, PageantListenerClient *plc);
 void pageant_listener_got_socket(struct pageant_listen_state *pl, Socket *);
-void pageant_listener_set_logfn(struct pageant_listen_state *pl,
-                                void *logctx, pageant_logfn_t logfn);
 void pageant_listener_free(struct pageant_listen_state *pl);
 
 /*
@@ -96,10 +192,6 @@ void pageant_listener_free(struct pageant_listen_state *pl);
  * process. (On at least one platform we want to do this in an
  * agnostic way between the two situations.)
  *
- * pageant_get_keylist{1,2} work just like pageant_make_keylist{1,2}
- * above, except that they can also cope if they have to contact an
- * external agent.
- *
  * pageant_add_keyfile() is used to load a private key from a file and
  * add it to the agent. Initially, you should call it with passphrase
  * NULL, and it will check if the key is already in the agent, and
@@ -114,15 +206,15 @@ void pageant_listener_free(struct pageant_listen_state *pl);
  * for keys that have the same trust properties). Call
  * pageant_forget_passphrases() to get rid of them all.
  */
-void *pageant_get_keylist1(int *length);
-void *pageant_get_keylist2(int *length);
 enum {
     PAGEANT_ACTION_OK,       /* success; no further action needed */
     PAGEANT_ACTION_FAILURE,  /* failure; *retstr is error message */
-    PAGEANT_ACTION_NEED_PP   /* need passphrase: *retstr is key comment */
+    PAGEANT_ACTION_NEED_PP,  /* need passphrase: *retstr is key comment */
+    PAGEANT_ACTION_WARNING,  /* success but with a warning message;
+                              * *retstr is warning message */
 };
 int pageant_add_keyfile(Filename *filename, const char *passphrase,
-                        char **retstr);
+                        char **retstr, bool add_encrypted);
 void pageant_forget_passphrases(void);
 
 struct pageant_pubkey {
@@ -136,11 +228,30 @@ struct pageant_pubkey {
 struct pageant_pubkey *pageant_pubkey_copy(struct pageant_pubkey *key);
 void pageant_pubkey_free(struct pageant_pubkey *key);
 
-typedef void (*pageant_key_enum_fn_t)(void *ctx,
-                                      const char *fingerprint,
-                                      const char *comment,
+typedef void (*pageant_key_enum_fn_t)(void *ctx, char **fingerprints,
+                                      const char *comment, uint32_t ext_flags,
                                       struct pageant_pubkey *key);
 int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx,
                       char **retstr);
 int pageant_delete_key(struct pageant_pubkey *key, char **retstr);
 int pageant_delete_all_keys(char **retstr);
+int pageant_reencrypt_key(struct pageant_pubkey *key, char **retstr);
+int pageant_reencrypt_all_keys(char **retstr);
+int pageant_sign(struct pageant_pubkey *key, ptrlen message, strbuf *out,
+                 uint32_t flags, char **retstr);
+
+/*
+ * Definitions for agent protocol extensions.
+ */
+#define PUTTYEXT(base) base "@putty.projects.tartarus.org"
+
+#define KNOWN_EXTENSIONS(X)                             \
+    X(EXT_QUERY, "query")                               \
+    X(EXT_ADD_PPK, PUTTYEXT("add-ppk"))                 \
+    X(EXT_REENCRYPT, PUTTYEXT("reencrypt"))             \
+    X(EXT_REENCRYPT_ALL, PUTTYEXT("reencrypt-all"))     \
+    X(EXT_LIST_EXTENDED, PUTTYEXT("list-extended"))     \
+    /* end of list */
+
+#define LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE    1
+#define LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY      2

+ 39 - 42
source/putty/portfwd.c

@@ -95,13 +95,13 @@ static void free_portlistener_state(struct PortListener *pl)
     sfree(pl);
 }
 
-static void pfd_log(Plug *plug, int type, SockAddr *addr, int port,
+static void pfd_log(Plug *plug, PlugLogType type, SockAddr *addr, int port,
                     const char *error_msg, int error_code)
 {
     /* we have to dump these since we have no interface to logging.c */
 }
 
-static void pfl_log(Plug *plug, int type, SockAddr *addr, int port,
+static void pfl_log(Plug *plug, PlugLogType type, SockAddr *addr, int port,
                     const char *error_msg, int error_code)
 {
     /* we have to dump these since we have no interface to logging.c */
@@ -408,7 +408,7 @@ static void pfd_receive(Plug *plug, int urgent, const char *data, size_t len)
          * Freeze the socket until the SSH server confirms the
          * connection.
          */
-        sk_set_frozen(pf->s, 1);
+        sk_set_frozen(pf->s, true);
 
         pf->c = wrap_lportfwd_open(pf->cl, pf->hostname, pf->port, pf->s,
                                    &pf->chan);
@@ -427,11 +427,10 @@ static void pfd_sent(Plug *plug, size_t bufsize)
 }
 
 static const PlugVtable PortForwarding_plugvt = {
-    pfd_log,
-    pfd_closing,
-    pfd_receive,
-    pfd_sent,
-    NULL
+    .log = pfd_log,
+    .closing = pfd_closing,
+    .receive = pfd_receive,
+    .sent = pfd_sent,
 };
 
 static void pfd_chan_free(Channel *chan);
@@ -443,32 +442,32 @@ static void pfd_send_eof(Channel *chan);
 static void pfd_set_input_wanted(Channel *chan, bool wanted);
 static char *pfd_log_close_msg(Channel *chan);
 
-static const struct ChannelVtable PortForwarding_channelvt = {
-    pfd_chan_free,
-    pfd_open_confirmation,
-    pfd_open_failure,
-    pfd_send,
-    pfd_send_eof,
-    pfd_set_input_wanted,
-    pfd_log_close_msg,
-    chan_default_want_close,
-    chan_no_exit_status,
-    chan_no_exit_signal,
-    chan_no_exit_signal_numeric,
-    chan_no_run_shell,
-    chan_no_run_command,
-    chan_no_run_subsystem,
-    chan_no_enable_x11_forwarding,
-    chan_no_enable_agent_forwarding,
-    chan_no_allocate_pty,
-    chan_no_set_env,
-    chan_no_send_break,
-    chan_no_send_signal,
-    chan_no_change_window_size,
-    chan_no_request_response,
+static const ChannelVtable PortForwarding_channelvt = {
+    .free = pfd_chan_free,
+    .open_confirmation = pfd_open_confirmation,
+    .open_failed = pfd_open_failure,
+    .send = pfd_send,
+    .send_eof = pfd_send_eof,
+    .set_input_wanted = pfd_set_input_wanted,
+    .log_close_msg = pfd_log_close_msg,
+    .want_close = chan_default_want_close,
+    .rcvd_exit_status = chan_no_exit_status,
+    .rcvd_exit_signal = chan_no_exit_signal,
+    .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
+    .run_shell = chan_no_run_shell,
+    .run_command = chan_no_run_command,
+    .run_subsystem = chan_no_run_subsystem,
+    .enable_x11_forwarding = chan_no_enable_x11_forwarding,
+    .enable_agent_forwarding = chan_no_enable_agent_forwarding,
+    .allocate_pty = chan_no_allocate_pty,
+    .set_env = chan_no_set_env,
+    .send_break = chan_no_send_break,
+    .send_signal = chan_no_send_signal,
+    .change_window_size = chan_no_change_window_size,
+    .request_response = chan_no_request_response,
 };
 
-Channel *portfwd_raw_new(ConnectionLayer *cl, Plug **plug)
+Channel *portfwd_raw_new(ConnectionLayer *cl, Plug **plug, bool start_ready)
 {
     struct PortForwarding *pf;
 
@@ -482,7 +481,7 @@ Channel *portfwd_raw_new(ConnectionLayer *cl, Plug **plug)
 
     pf->cl = cl;
     pf->input_wanted = true;
-    pf->ready = false;
+    pf->ready = start_ready;
 
     pf->socks_state = SOCKS_NONE;
     pf->hostname = NULL;
@@ -523,7 +522,7 @@ static int pfl_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
     Socket *s;
     const char *err;
 
-    chan = portfwd_raw_new(pl->cl, &plug);
+    chan = portfwd_raw_new(pl->cl, &plug, false);
     s = constructor(ctx, plug);
     if ((err = sk_socket_error(s)) != NULL) {
         portfwd_raw_free(chan);
@@ -538,7 +537,7 @@ static int pfl_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
         pf->socksbuf = strbuf_new();
         pf->socksbuf_consumed = 0;
         pf->port = 0;                  /* "hostname" buffer is so far empty */
-        sk_set_frozen(s, 0);           /* we want to receive SOCKS _now_! */
+        sk_set_frozen(s, false);       /* we want to receive SOCKS _now_! */
     } else {
         pf->hostname = dupstr(pl->hostname);
         pf->port = pl->port;
@@ -551,11 +550,9 @@ static int pfl_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
 }
 
 static const PlugVtable PortListener_plugvt = {
-    pfl_log,
-    pfl_closing,
-    NULL,                          /* recv */
-    NULL,                          /* send */
-    pfl_accepting
+    .log = pfl_log,
+    .closing = pfl_closing,
+    .accepting = pfl_accepting,
 };
 
 /*
@@ -666,7 +663,7 @@ static void pfd_open_confirmation(Channel *chan)
     PortForwarding *pf = container_of(chan, PortForwarding, chan);
 
     pf->ready = true;
-    sk_set_frozen(pf->s, 0);
+    sk_set_frozen(pf->s, false);
     sk_write(pf->s, NULL, 0);
     if (pf->socksbuf) {
         sshfwd_write(pf->c, pf->socksbuf->u + pf->socksbuf_consumed,
@@ -732,7 +729,7 @@ static int pfr_cmp(void *av, void *bv)
     return 0;
 }
 
-void pfr_free(PortFwdRecord *pfr)
+static void pfr_free(PortFwdRecord *pfr)
 {
     /* Dispose of any listening socket. */
     if (pfr->local)

+ 72 - 76
source/putty/proxy.c

@@ -65,7 +65,7 @@ void proxy_activate (ProxySocket *p)
      * unfreezing the actual underlying socket.
      */
     if (!p->freeze)
-        sk_set_frozen(&p->sock, 0);
+        sk_set_frozen(&p->sock, false);
 }
 
 /* basic proxy socket functions */
@@ -171,8 +171,8 @@ static const char * sk_proxy_socket_error (Socket *s)
 
 /* basic proxy plug functions */
 
-static void plug_proxy_log(Plug *plug, int type, SockAddr *addr, int port,
-                           const char *error_msg, int error_code)
+static void plug_proxy_log(Plug *plug, PlugLogType type, SockAddr *addr,
+                           int port, const char *error_msg, int error_code)
 {
     ProxySocket *ps = container_of(plug, ProxySocket, plugimpl);
 
@@ -371,23 +371,23 @@ SockAddr *name_lookup(const char *host, int port, char **canonicalname,
     }
 }
 
-static const struct SocketVtable ProxySocket_sockvt = {
-    sk_proxy_plug,
-    sk_proxy_close,
-    sk_proxy_write,
-    sk_proxy_write_oob,
-    sk_proxy_write_eof,
-    sk_proxy_set_frozen,
-    sk_proxy_socket_error,
-    NULL, /* peer_info */
+static const SocketVtable ProxySocket_sockvt = {
+    .plug = sk_proxy_plug,
+    .close = sk_proxy_close,
+    .write = sk_proxy_write,
+    .write_oob = sk_proxy_write_oob,
+    .write_eof = sk_proxy_write_eof,
+    .set_frozen = sk_proxy_set_frozen,
+    .socket_error = sk_proxy_socket_error,
+    .peer_info = NULL,
 };
 
-static const struct PlugVtable ProxySocket_plugvt = {
-    plug_proxy_log,
-    plug_proxy_closing,
-    plug_proxy_receive,
-    plug_proxy_sent,
-    plug_proxy_accepting
+static const PlugVtable ProxySocket_plugvt = {
+    .log = plug_proxy_log,
+    .closing = plug_proxy_closing,
+    .receive = plug_proxy_receive,
+    .sent = plug_proxy_sent,
+    .accepting = plug_proxy_accepting
 };
 
 Socket *new_connection(SockAddr *addr, const char *hostname,
@@ -407,8 +407,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname,
 
         if ((sret = platform_new_connection(addr, hostname, port, privport,
                                             oobinline, nodelay, keepalive,
-                                            plug, conf)) !=
-            NULL)
+                                            plug, conf)) != NULL)
             return sret;
 
         ret = snew(ProxySocket);
@@ -455,7 +454,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname,
                                       conf_get_str(conf, CONF_proxy_host),
                                       conf_get_int(conf, CONF_proxy_port),
                                       hostname, port);
-            plug_log(plug, 2, NULL, 0, logmsg, 0);
+            plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0);
             sfree(logmsg);
         }
 
@@ -463,7 +462,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname,
             char *logmsg = dns_log_msg(conf_get_str(conf, CONF_proxy_host),
                                        conf_get_int(conf, CONF_addressfamily),
                                        "proxy");
-            plug_log(plug, 2, NULL, 0, logmsg, 0);
+            plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0);
             sfree(logmsg);
         }
 
@@ -484,7 +483,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname,
             logmsg = dupprintf("Connecting to %s proxy at %s port %d",
                                proxy_type, addrbuf,
                                conf_get_int(conf, CONF_proxy_port));
-            plug_log(plug, 2, NULL, 0, logmsg, 0);
+            plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0);
             sfree(logmsg);
         }
 
@@ -504,7 +503,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname,
             return &ret->sock;
 
         /* start the proxy negotiation process... */
-        sk_set_frozen(ret->sub_socket, 0);
+        sk_set_frozen(ret->sub_socket, false);
         ret->negotiate(ret, PROXY_CHANGE_NEW);
 
         return &ret->sock;
@@ -776,13 +775,12 @@ int proxy_socks4_negotiate (ProxySocket *p, int change)
         put_uint16(command, p->remote_port);
 
         switch (sk_addrtype(p->remote_addr)) {
-          case ADDRTYPE_IPV4:
-            {
-                char addr[4];
-                sk_addrcopy(p->remote_addr, addr);
-                put_data(command, addr, 4);
-                break;
-            }
+          case ADDRTYPE_IPV4: {
+            char addr[4];
+            sk_addrcopy(p->remote_addr, addr);
+            put_data(command, addr, 4);
+            break;
+          }
           case ADDRTYPE_NAME:
             sk_getaddr(p->remote_addr, hostname, lenof(hostname));
             put_uint32(command, 1);
@@ -1094,19 +1092,18 @@ int proxy_socks5_negotiate (ProxySocket *p, int change)
                 put_byte(command, 4);  /* IPv6 */
                 sk_addrcopy(p->remote_addr, strbuf_append(command, 16));
                 break;
-              case ADDRTYPE_NAME:
-                {
-                    char hostname[512];
-                    put_byte(command, 3);  /* domain name */
-                    sk_getaddr(p->remote_addr, hostname, lenof(hostname));
-                    if (!put_pstring(command, hostname)) {
-                        p->error = "Proxy error: SOCKS 5 cannot "
-                            "support host names longer than 255 chars";
-                        strbuf_free(command);
-                        return 1;
-                    }
+              case ADDRTYPE_NAME: {
+                char hostname[512];
+                put_byte(command, 3);  /* domain name */
+                sk_getaddr(p->remote_addr, hostname, lenof(hostname));
+                if (!put_pstring(command, hostname)) {
+                  p->error = "Proxy error: SOCKS 5 cannot "
+                      "support host names longer than 255 chars";
+                  strbuf_free(command);
+                  return 1;
                 }
                 break;
+              }
             }
 
             put_uint16(command, p->remote_port);
@@ -1327,41 +1324,40 @@ char *format_telnet_command(SockAddr *addr, int port, Conf *conf)
                 break;
 
               case 'x':
-              case 'X':
-                {
-                    /* escaped hexadecimal value (ie. \xff) */
-                    unsigned char v = 0;
-                    int i = 0;
-
-                    for (;;) {
-                        eo++;
-                        if (fmt[eo] >= '0' && fmt[eo] <= '9')
-                            v += fmt[eo] - '0';
-                        else if (fmt[eo] >= 'a' && fmt[eo] <= 'f')
-                            v += fmt[eo] - 'a' + 10;
-                        else if (fmt[eo] >= 'A' && fmt[eo] <= 'F')
-                            v += fmt[eo] - 'A' + 10;
-                        else {
-                            /* non hex character, so we abort and just
-                             * send the whole thing unescaped (including \x)
-                             */
-                            put_byte(buf, '\\');
-                            eo = so + 1;
-                            break;
-                        }
-
-                        /* we only extract two hex characters */
-                        if (i == 1) {
-                            put_byte(buf, v);
-                            eo++;
-                            break;
-                        }
-
-                        i++;
-                        v <<= 4;
-                    }
+              case 'X': {
+                /* escaped hexadecimal value (ie. \xff) */
+                unsigned char v = 0;
+                int i = 0;
+
+                for (;;) {
+                  eo++;
+                  if (fmt[eo] >= '0' && fmt[eo] <= '9')
+                      v += fmt[eo] - '0';
+                  else if (fmt[eo] >= 'a' && fmt[eo] <= 'f')
+                      v += fmt[eo] - 'a' + 10;
+                  else if (fmt[eo] >= 'A' && fmt[eo] <= 'F')
+                      v += fmt[eo] - 'A' + 10;
+                  else {
+                    /* non hex character, so we abort and just
+                     * send the whole thing unescaped (including \x)
+                     */
+                    put_byte(buf, '\\');
+                    eo = so + 1;
+                    break;
+                  }
+
+                  /* we only extract two hex characters */
+                  if (i == 1) {
+                    put_byte(buf, v);
+                    eo++;
+                    break;
+                  }
+
+                  i++;
+                  v <<= 4;
                 }
                 break;
+              }
 
               default:
                 put_data(buf, fmt + so, 2);
@@ -1466,7 +1462,7 @@ int proxy_telnet_negotiate (ProxySocket *p, int change)
             *out = '\0';
 
             logmsg = dupprintf("Sending Telnet proxy command: %s", reescaped);
-            plug_log(p->plug, 2, NULL, 0, logmsg, 0);
+            plug_log(p->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0);
             sfree(logmsg);
             sfree(reescaped);
         }

+ 343 - 140
source/putty/putty.h

@@ -4,19 +4,6 @@
 #include <stddef.h>                    /* for wchar_t */
 #include <limits.h>                    /* for INT_MAX */
 
-/*
- * Global variables. Most modules declare these `extern', but
- * window.c will do `#define PUTTY_DO_GLOBALS' before including this
- * module, and so will get them properly defined.
- */
-#ifndef GLOBAL
-#ifdef PUTTY_DO_GLOBALS
-#define GLOBAL
-#else
-#define GLOBAL extern
-#endif
-#endif
-
 #include "defs.h"
 #include "puttyps.h"
 #include "network.h"
@@ -43,6 +30,151 @@
 #define PGP_PREV_MASTER_KEY_FP                                  \
     "440D E3B5 B7A1 CA85 B3CC  1718 AB58 5DC6 0467 6F7C"
 
+/*
+ * Definitions of three separate indexing schemes for colour palette
+ * entries.
+ *
+ * Why three? Because history, sorry.
+ *
+ * Two of the colour indexings are used in escape sequences. The
+ * Linux-console style OSC P sequences for setting the palette use an
+ * indexing in which the eight standard ANSI SGR colours come first,
+ * then their bold versions, and then six extra colours for default
+ * fg/bg and the terminal cursor. And the xterm OSC 4 sequences for
+ * querying the palette use a related indexing in which the six extra
+ * colours are pushed up to indices 256 and onwards, with the previous
+ * 16 being the first part of the xterm 256-colour space, and 240
+ * additional terminal-accessible colours inserted in the middle.
+ *
+ * The third indexing is the order that the colours appear in the
+ * PuTTY configuration panel, and also the order in which they're
+ * described in the saved session files. This order specifies the same
+ * set of colours as the OSC P encoding, but in a different order,
+ * with the default fg/bg colours (which users are most likely to want
+ * to reconfigure) at the start, and the ANSI SGR colours coming
+ * later.
+ *
+ * So all three indices really are needed, because all three appear in
+ * protocols or file formats outside the PuTTY binary. (Changing the
+ * saved-session encoding would have a backwards-compatibility impact;
+ * also, if we ever do, it would be better to replace the numeric
+ * indices with descriptive keywords.)
+ *
+ * Since the OSC 4 encoding contains the full set of colours used in
+ * the terminal display, that's the encoding used by front ends to
+ * store any actual data associated with their palette entries. So the
+ * TermWin palette_set and palette_get_overrides methods use that
+ * encoding, and so does the bitwise encoding of attribute words used
+ * in terminal redraw operations.
+ *
+ * The Conf encoding, of course, is used by config.c and settings.c.
+ *
+ * The aim is that those two sections of the code should never need to
+ * come directly into contact, and the only module that should have to
+ * deal directly with the mapping between these colour encodings - or
+ * to deal _at all_ with the intermediate OSC P encoding - is
+ * terminal.c itself.
+ */
+
+#define CONF_NCOLOURS 22               /* 16 + 6 special ones */
+#define OSCP_NCOLOURS 22               /* same as CONF, but different order */
+#define OSC4_NCOLOURS 262              /* 256 + the same 6 special ones */
+
+/* The list macro for the conf colours also gives the textual names
+ * used in the GUI configurer */
+#define CONF_COLOUR_LIST(X)                     \
+    X(fg, "Default Foreground")                 \
+    X(fg_bold, "Default Bold Foreground")       \
+    X(bg, "Default Background")                 \
+    X(bg_bold, "Default Bold Background")       \
+    X(cursor_fg, "Cursor Text")                 \
+    X(cursor_bg, "Cursor Colour")               \
+    X(black, "ANSI Black")                      \
+    X(black_bold, "ANSI Black Bold")            \
+    X(red, "ANSI Red")                          \
+    X(red_bold, "ANSI Red Bold")                \
+    X(green, "ANSI Green")                      \
+    X(green_bold, "ANSI Green Bold")            \
+    X(yellow, "ANSI Yellow")                    \
+    X(yellow_bold, "ANSI Yellow Bold")          \
+    X(blue, "ANSI Blue")                        \
+    X(blue_bold, "ANSI Blue Bold")              \
+    X(magenta, "ANSI Magenta")                  \
+    X(magenta_bold, "ANSI Magenta Bold")        \
+    X(cyan, "ANSI Cyan")                        \
+    X(cyan_bold, "ANSI Cyan Bold")              \
+    X(white, "ANSI White")                      \
+    X(white_bold, "ANSI White Bold")            \
+    /* end of list */
+
+#define OSCP_COLOUR_LIST(X)                     \
+    X(black)                                    \
+    X(red)                                      \
+    X(green)                                    \
+    X(yellow)                                   \
+    X(blue)                                     \
+    X(magenta)                                  \
+    X(cyan)                                     \
+    X(white)                                    \
+    X(black_bold)                               \
+    X(red_bold)                                 \
+    X(green_bold)                               \
+    X(yellow_bold)                              \
+    X(blue_bold)                                \
+    X(magenta_bold)                             \
+    X(cyan_bold)                                \
+    X(white_bold)                               \
+    /*
+     * In the OSC 4 indexing, this is where the extra 240 colours go.
+     * They consist of:
+     *
+     *  - 216 colours forming a 6x6x6 cube, with R the most
+     *    significant colour and G the least. In other words, these
+     *    occupy the space of indices 16 <= i < 232, with each
+     *    individual colour found as i = 16 + 36*r + 6*g + b, for all
+     *    0 <= r,g,b <= 5.
+     *
+     *  - The remaining indices, 232 <= i < 256, consist of a uniform
+     *    series of grey shades running between black and white (but
+     *    not including either, since actual black and white are
+     *    already provided in the previous colour cube).
+     *
+     * After that, we have the remaining 6 special colours:
+     */                                         \
+    X(fg)                                       \
+    X(fg_bold)                                  \
+    X(bg)                                       \
+    X(bg_bold)                                  \
+    X(cursor_fg)                                \
+    X(cursor_bg)                                \
+    /* end of list */
+
+/* Enumerations of the colour lists. These are available everywhere in
+ * the code. The OSC P encoding shouldn't be used outside terminal.c,
+ * but the easiest way to define the OSC 4 enum is to have the OSC P
+ * one available to compute with. */
+enum {
+    #define ENUM_DECL(id,name) CONF_COLOUR_##id,
+    CONF_COLOUR_LIST(ENUM_DECL)
+    #undef ENUM_DECL
+};
+enum {
+    #define ENUM_DECL(id) OSCP_COLOUR_##id,
+    OSCP_COLOUR_LIST(ENUM_DECL)
+    #undef ENUM_DECL
+};
+enum {
+    #define ENUM_DECL(id) OSC4_COLOUR_##id = \
+        OSCP_COLOUR_##id + (OSCP_COLOUR_##id >= 16 ? 240 : 0),
+    OSCP_COLOUR_LIST(ENUM_DECL)
+    #undef ENUM_DECL
+};
+
+/* Mapping tables defined in terminal.c */
+extern const int colour_indices_conf_to_oscp[CONF_NCOLOURS];
+extern const int colour_indices_conf_to_osc4[CONF_NCOLOURS];
+extern const int colour_indices_oscp_to_osc4[OSCP_NCOLOURS];
+
 /* Three attribute types:
  * The ATTRs (normal attributes) are stored with the characters in
  * the main display arrays
@@ -84,9 +216,9 @@
 
 #define ATTR_INVALID 0x03FFFFU
 
-/* Like Linux use the F000 page for direct to font. */
-#define CSET_OEMCP   0x0000F000UL      /* OEM Codepage DTF */
-#define CSET_ACP     0x0000F100UL      /* Ansi Codepage DTF */
+/* Use the DC00 page for direct to font. */
+#define CSET_OEMCP   0x0000DC00UL      /* OEM Codepage DTF */
+#define CSET_ACP     0x0000DD00UL      /* Ansi Codepage DTF */
 
 /* These are internal use overlapping with the UTF-16 surrogates */
 #define CSET_ASCII   0x0000D800UL      /* normal ASCII charset ESC ( B */
@@ -96,7 +228,7 @@
 #define CSET_MASK    0xFFFFFF00UL      /* Character set mask */
 
 #define DIRECT_CHAR(c) ((c&0xFFFFFC00)==0xD800)
-#define DIRECT_FONT(c) ((c&0xFFFFFE00)==0xF000)
+#define DIRECT_FONT(c) ((c&0xFFFFFE00)==0xDC00)
 
 #define UCSERR       (CSET_LINEDRW|'a') /* UCS Format error character. */
 /*
@@ -115,34 +247,16 @@
 #define ATTR_UNDER   0x0080000U
 #define ATTR_REVERSE 0x0100000U
 #define ATTR_BLINK   0x0200000U
-#define ATTR_FGMASK  0x00001FFU
-#define ATTR_BGMASK  0x003FE00U
+#define ATTR_FGMASK  0x00001FFU /* stores a colour in OSC 4 indexing */
+#define ATTR_BGMASK  0x003FE00U /* stores a colour in OSC 4 indexing */
 #define ATTR_COLOURS 0x003FFFFU
 #define ATTR_DIM     0x1000000U
+#define ATTR_STRIKE  0x2000000U
 #define ATTR_FGSHIFT 0
 #define ATTR_BGSHIFT 9
 
-/*
- * The definitive list of colour numbers stored in terminal
- * attribute words is kept here. It is:
- *
- *  - 0-7 are ANSI colours (KRGYBMCW).
- *  - 8-15 are the bold versions of those colours.
- *  - 16-255 are the remains of the xterm 256-colour mode (a
- *    216-colour cube with R at most significant and B at least,
- *    followed by a uniform series of grey shades running between
- *    black and white but not including either on grounds of
- *    redundancy).
- *  - 256 is default foreground
- *  - 257 is default bold foreground
- *  - 258 is default background
- *  - 259 is default bold background
- *  - 260 is cursor foreground
- *  - 261 is cursor background
- */
-
-#define ATTR_DEFFG   (256 << ATTR_FGSHIFT)
-#define ATTR_DEFBG   (258 << ATTR_BGSHIFT)
+#define ATTR_DEFFG   (OSC4_COLOUR_fg << ATTR_FGSHIFT)
+#define ATTR_DEFBG   (OSC4_COLOUR_bg << ATTR_BGSHIFT)
 #define ATTR_DEFAULT (ATTR_DEFFG | ATTR_DEFBG)
 
 struct sesslist {
@@ -324,6 +438,7 @@ enum {
     HK_DSA,
     HK_ECDSA,
     HK_ED25519,
+    HK_ED448,
     HK_MAX
 };
 
@@ -376,12 +491,20 @@ enum {
     TITLE_NONE, TITLE_EMPTY, TITLE_REAL
 };
 
+enum {
+    /* SUPDUP character set options */
+    SUPDUP_CHARSET_ASCII, SUPDUP_CHARSET_ITS, SUPDUP_CHARSET_WAITS
+};
+
 enum {
     /* Protocol back ends. (CONF_protocol) */
-    PROT_RAW, PROT_TELNET, PROT_RLOGIN, PROT_SSH,
+    PROT_RAW, PROT_TELNET, PROT_RLOGIN, PROT_SSH, PROT_SSHCONN,
     /* PROT_SERIAL is supported on a subset of platforms, but it doesn't
      * hurt to define it globally. */
-    PROT_SERIAL
+    PROT_SERIAL,
+    /* PROT_SUPDUP is the historical RFC 734 protocol. */
+    PROT_SUPDUP,
+    PROTOCOL_LIMIT, /* upper bound on number of protocols */
 };
 
 enum {
@@ -490,14 +613,19 @@ enum {
     ADDRTYPE_NAME      /* SockAddr storing an unresolved host name */
 };
 
+/* Backend flags */
+#define BACKEND_RESIZE_FORBIDDEN    0x01   /* Backend does not allow
+                                              resizing terminal */
+#define BACKEND_NEEDS_TERMINAL      0x02   /* Backend must have terminal */
+
 struct Backend {
     const BackendVtable *vt;
 };
 struct BackendVtable {
-    const char *(*init) (Seat *seat, Backend **backend_out,
-                         LogContext *logctx, Conf *conf,
-                         const char *host, int port,
-                         char **realhost, bool nodelay, bool keepalive);
+    char *(*init) (const BackendVtable *vt, Seat *seat,
+                   Backend **backend_out, LogContext *logctx, Conf *conf,
+                   const char *host, int port, char **realhost,
+                   bool nodelay, bool keepalive);
 
     void (*free) (Backend *be);
     /* Pass in a replacement configuration. */
@@ -524,16 +652,31 @@ struct BackendVtable {
     /* Only implemented in the SSH protocol: check whether a
      * connection-sharing upstream exists for a given configuration. */
     bool (*test_for_upstream)(const char *host, int port, Conf *conf);
+    /* Special-purpose function to return additional information to put
+     * in a "are you sure you want to close this session" dialog;
+     * return NULL if no such info, otherwise caller must free.
+     * Only implemented in the SSH protocol, to warn about downstream
+     * connections that would be lost if this one were terminated. */
+    char *(*close_warn_text)(Backend *be);
+
+    /* 'id' is a machine-readable name for the backend, used in
+     * saved-session storage. 'displayname' is a human-readable name
+     * for error messages. */
+    const char *id, *displayname;
 
-    const char *name;
     int protocol;
     int default_port;
+    unsigned flags;
+
+    /* Only relevant for the serial protocol: bit masks of which
+     * parity and flow control settings are supported. */
+    unsigned serial_parity_mask, serial_flow_mask;
 };
 
-static inline const char *backend_init(
+static inline char *backend_init(
     const BackendVtable *vt, Seat *seat, Backend **out, LogContext *logctx,
     Conf *conf, const char *host, int port, char **rhost, bool nd, bool ka)
-{ return vt->init(seat, out, logctx, conf, host, port, rhost, nd, ka); }
+{ return vt->init(vt, seat, out, logctx, conf, host, port, rhost, nd, ka); }
 static inline void backend_free(Backend *be)
 { be->vt->free(be); }
 static inline void backend_reconfig(Backend *be, Conf *conf)
@@ -565,6 +708,12 @@ static inline int backend_cfg_info(Backend *be)
 { return be->vt->cfg_info(be); }
 
 extern const struct BackendVtable *const backends[];
+/*
+ * In programs with a config UI, only the first few members of
+ * backends[] will be displayed at the top-level; the others will be
+ * relegated to a drop-down.
+ */
+extern const size_t n_ui_backends;
 
 /*
  * Suggested default protocol provided by the backend link module.
@@ -578,45 +727,6 @@ extern const int be_default_protocol;
  */
 extern const char *const appname;
 
-/*
- * Some global flags denoting the type of application.
- *
- * FLAG_VERBOSE is set when the user requests verbose details.
- *
- * FLAG_INTERACTIVE is set when a full interactive shell session is
- * being run, _either_ because no remote command has been provided
- * _or_ because the application is GUI and can't run non-
- * interactively.
- *
- * These flags describe the type of _application_ - they wouldn't
- * vary between individual sessions - and so it's OK to have this
- * variable be GLOBAL.
- *
- * Note that additional flags may be defined in platform-specific
- * headers. It's probably best if those ones start from 0x1000, to
- * avoid collision.
- */
-#define FLAG_VERBOSE     0x0001
-#define FLAG_INTERACTIVE 0x0002
-GLOBAL int flags;
-
-/*
- * Likewise, these two variables are set up when the application
- * initialises, and inform all default-settings accesses after
- * that.
- */
-GLOBAL int default_protocol;
-GLOBAL int default_port;
-
-/*
- * This is set true by cmdline.c iff a session is loaded with "-load".
- */
-GLOBAL bool loaded_session;
-/*
- * This is set to the name of the loaded session.
- */
-GLOBAL char *cmdline_session_name;
-
 /*
  * Mechanism for getting text strings such as usernames and passwords
  * from the front-end.
@@ -868,8 +978,8 @@ struct SeatVtable {
      *    or +1'.
      */
     int (*verify_ssh_host_key)(
-        Seat *seat, const char *host, int port,
-        const char *keytype, char *keystr, char *key_fingerprint,
+        Seat *seat, const char *host, int port, const char *keytype,
+        char *keystr, const char *keydisp, char **key_fingerprints,
         void (*callback)(void *ctx, int result), void *ctx);
 
     /*
@@ -954,6 +1064,23 @@ struct SeatVtable {
      * prompts by malicious servers.
      */
     bool (*set_trust_status)(Seat *seat, bool trusted);
+
+    /*
+     * Ask the seat whether it would like verbose messages.
+     */
+    bool (*verbose)(Seat *seat);
+
+    /*
+     * Ask the seat whether it's an interactive program.
+     */
+    bool (*interactive)(Seat *seat);
+
+    /*
+     * Return the seat's current idea of where the output cursor is.
+     *
+     * Returns true if the seat has a cursor. Returns false if not.
+     */
+    bool (*get_cursor_position)(Seat *seat, int *x, int *y);
 };
 
 static inline size_t seat_output(
@@ -974,8 +1101,9 @@ static inline void seat_set_busy_status(Seat *seat, BusyStatus status)
 { seat->vt->set_busy_status(seat, status); }
 static inline int seat_verify_ssh_host_key(
     Seat *seat, const char *h, int p, const char *ktyp, char *kstr,
-    char *fp, void (*cb)(void *ctx, int result), void *ctx)
-{ return seat->vt->verify_ssh_host_key(seat, h, p, ktyp, kstr, fp, cb, ctx); }
+    const char *kdsp, char **fps, void (*cb)(void *ctx, int result), void *ctx)
+{ return seat->vt->verify_ssh_host_key(seat, h, p, ktyp, kstr, kdsp, fps,
+                                       cb, ctx); }
 static inline int seat_confirm_weak_crypto_primitive(
     Seat *seat, const char *atyp, const char *aname,
     void (*cb)(void *ctx, int result), void *ctx)
@@ -999,6 +1127,12 @@ static inline StripCtrlChars *seat_stripctrl_new(
 { return seat->vt->stripctrl_new(seat, bs, sic); }
 static inline bool seat_set_trust_status(Seat *seat, bool trusted)
 { return  seat->vt->set_trust_status(seat, trusted); }
+static inline bool seat_verbose(Seat *seat)
+{ return seat->vt->verbose(seat); }
+static inline bool seat_interactive(Seat *seat)
+{ return seat->vt->interactive(seat); }
+static inline bool seat_get_cursor_position(Seat *seat, int *x, int *y)
+{ return  seat->vt->get_cursor_position(seat, x, y); }
 
 /* Unlike the seat's actual method, the public entry point
  * seat_connection_fatal is a wrapper function with a printf-like API,
@@ -1032,8 +1166,8 @@ void nullseat_update_specials_menu(Seat *seat);
 char *nullseat_get_ttymode(Seat *seat, const char *mode);
 void nullseat_set_busy_status(Seat *seat, BusyStatus status);
 int nullseat_verify_ssh_host_key(
-    Seat *seat, const char *host, int port,
-    const char *keytype, char *keystr, char *key_fingerprint,
+    Seat *seat, const char *host, int port, const char *keytype,
+    char *keystr, const char *keydisp, char **key_fingerprints,
     void (*callback)(void *ctx, int result), void *ctx);
 int nullseat_confirm_weak_crypto_primitive(
     Seat *seat, const char *algtype, const char *algname,
@@ -1051,6 +1185,11 @@ StripCtrlChars *nullseat_stripctrl_new(
         Seat *seat, BinarySink *bs_out, SeatInteractionContext sic);
 bool nullseat_set_trust_status(Seat *seat, bool trusted);
 bool nullseat_set_trust_status_vacuously(Seat *seat, bool trusted);
+bool nullseat_verbose_no(Seat *seat);
+bool nullseat_verbose_yes(Seat *seat);
+bool nullseat_interactive_no(Seat *seat);
+bool nullseat_interactive_yes(Seat *seat);
+bool nullseat_get_cursor_position(Seat *seat, int *x, int *y);
 
 /*
  * Seat functions provided by the platform's console-application
@@ -1059,8 +1198,8 @@ bool nullseat_set_trust_status_vacuously(Seat *seat, bool trusted);
 
 void console_connection_fatal(Seat *seat, const char *message);
 int console_verify_ssh_host_key(
-    Seat *seat, const char *host, int port,
-    const char *keytype, char *keystr, char *key_fingerprint,
+    Seat *seat, const char *host, int port, const char *keytype,
+    char *keystr, const char *keydisp, char **key_fingerprints,
     void (*callback)(void *ctx, int result), void *ctx);
 int console_confirm_weak_crypto_primitive(
     Seat *seat, const char *algtype, const char *algname,
@@ -1076,6 +1215,11 @@ bool console_set_trust_status(Seat *seat, bool trusted);
  * Other centralised seat functions.
  */
 int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input);
+bool cmdline_seat_verbose(Seat *seat);
+
+typedef struct rgb {
+    uint8_t r, g, b;
+} rgb;
 
 /*
  * Data type 'TermWin', which is a vtable encapsulating all the
@@ -1115,7 +1259,13 @@ struct TermWinVtable {
 
     void (*set_cursor_pos)(TermWin *, int x, int y);
 
+    /* set_raw_mouse_mode instructs the front end to start sending mouse events
+     * in raw mode suitable for translating into mouse-tracking terminal data
+     * (e.g. include scroll-wheel events and don't bother to identify double-
+     * and triple-clicks). set_raw_mouse_mode_pointer instructs the front end
+     * to change the mouse pointer shape to *indicate* raw mouse mode. */
     void (*set_raw_mouse_mode)(TermWin *, bool enable);
+    void (*set_raw_mouse_mode_pointer)(TermWin *, bool enable);
 
     void (*set_scrollbar)(TermWin *, int total, int start, int page);
 
@@ -1137,19 +1287,20 @@ struct TermWinVtable {
      * the window it remembers whether to go back to normal or
      * maximised. */
     void (*set_minimised)(TermWin *, bool minimised);
-    bool (*is_minimised)(TermWin *);
     void (*set_maximised)(TermWin *, bool maximised);
     void (*move)(TermWin *, int x, int y);
     void (*set_zorder)(TermWin *, bool top);
 
-    bool (*palette_get)(TermWin *, int n, int *r, int *g, int *b);
-    void (*palette_set)(TermWin *, int n, int r, int g, int b);
-    void (*palette_reset)(TermWin *);
+    /* Set the colour palette that the TermWin will use to display
+     * text. One call to this function sets 'ncolours' consecutive
+     * colours in the OSC 4 sequence, starting at 'start'. */
+    void (*palette_set)(TermWin *, unsigned start, unsigned ncolours,
+                        const rgb *colours);
 
-    void (*get_pos)(TermWin *, int *x, int *y);
-    void (*get_pixels)(TermWin *, int *x, int *y);
-    const char *(*get_title)(TermWin *, bool icon);
-    bool (*is_utf8)(TermWin *);
+    /* Query the front end for any OS-local overrides to the default
+     * colours stored in Conf. The front end should set any it cares
+     * about by calling term_palette_override. */
+    void (*palette_get_overrides)(TermWin *);
 };
 
 static inline bool win_setup_draw_ctx(TermWin *win)
@@ -1172,6 +1323,8 @@ static inline void win_set_cursor_pos(TermWin *win, int x, int y)
 { win->vt->set_cursor_pos(win, x, y); }
 static inline void win_set_raw_mouse_mode(TermWin *win, bool enable)
 { win->vt->set_raw_mouse_mode(win, enable); }
+static inline void win_set_raw_mouse_mode_pointer(TermWin *win, bool enable)
+{ win->vt->set_raw_mouse_mode_pointer(win, enable); }
 static inline void win_set_scrollbar(TermWin *win, int t, int s, int p)
 { win->vt->set_scrollbar(win, t, s, p); }
 static inline void win_bell(TermWin *win, int mode)
@@ -1192,28 +1345,17 @@ static inline void win_set_icon_title(TermWin *win, const char *icontitle)
 { win->vt->set_icon_title(win, icontitle); }
 static inline void win_set_minimised(TermWin *win, bool minimised)
 { win->vt->set_minimised(win, minimised); }
-static inline bool win_is_minimised(TermWin *win)
-{ return win->vt->is_minimised(win); }
 static inline void win_set_maximised(TermWin *win, bool maximised)
 { win->vt->set_maximised(win, maximised); }
 static inline void win_move(TermWin *win, int x, int y)
 { win->vt->move(win, x, y); }
 static inline void win_set_zorder(TermWin *win, bool top)
 { win->vt->set_zorder(win, top); }
-static inline bool win_palette_get(TermWin *win, int n, int *r, int *g, int *b)
-{ return win->vt->palette_get(win, n, r, g, b); }
-static inline void win_palette_set(TermWin *win, int n, int r, int g, int b)
-{ win->vt->palette_set(win, n, r, g, b); }
-static inline void win_palette_reset(TermWin *win)
-{ win->vt->palette_reset(win); }
-static inline void win_get_pos(TermWin *win, int *x, int *y)
-{ win->vt->get_pos(win, x, y); }
-static inline void win_get_pixels(TermWin *win, int *x, int *y)
-{ win->vt->get_pixels(win, x, y); }
-static inline const char *win_get_title(TermWin *win, bool icon)
-{ return win->vt->get_title(win, icon); }
-static inline bool win_is_utf8(TermWin *win)
-{ return win->vt->is_utf8(win); }
+static inline void win_palette_set(
+    TermWin *win, unsigned start, unsigned ncolours, const rgb *colours)
+{ win->vt->palette_set(win, start, ncolours, colours); }
+static inline void win_palette_get_overrides(TermWin *win)
+{ win->vt->palette_get_overrides(win); }
 
 /*
  * Global functions not specific to a connection instance.
@@ -1311,6 +1453,11 @@ NORETURN void cleanup_exit(int);
     X(INT, NONE, serstopbits) \
     X(INT, NONE, serparity) /* SER_PAR_NONE, SER_PAR_ODD, ... */ \
     X(INT, NONE, serflow) /* SER_FLOW_NONE, SER_FLOW_XONXOFF, ... */ \
+    /* Supdup options */ \
+    X(STR, NONE, supdup_location) \
+    X(INT, NONE, supdup_ascii_set) \
+    X(BOOL, NONE, supdup_more) \
+    X(BOOL, NONE, supdup_scroll) \
     /* Keyboard options */ \
     X(BOOL, NONE, bksp_is_delete) \
     X(BOOL, NONE, rxvt_homeend) \
@@ -1391,7 +1538,7 @@ NORETURN void cleanup_exit(int);
     X(BOOL, NONE, system_colour) \
     X(BOOL, NONE, try_palette) \
     X(INT, NONE, bold_style) /* 1=font 2=colour (3=both) */ \
-    X(INT, INT, colours) \
+    X(INT, INT, colours) /* indexed by the CONF_COLOUR_* enum encoding */ \
     /* Selection options */ \
     X(INT, NONE, mouse_is_xterm) /* 0=compromise 1=xterm 2=Windows */ \
     X(BOOL, NONE, rect_select) \
@@ -1487,8 +1634,6 @@ NORETURN void cleanup_exit(int);
 enum config_primary_key { CONFIG_OPTIONS(CONF_ENUM_DEF) N_CONFIG_OPTIONS };
 #undef CONF_ENUM_DEF
 
-#define NCFGCOLOURS 22 /* number of colours in CONF_colours above */
-
 /* Functions handling configuration structures. */
 Conf *conf_new(void);                  /* create an empty configuration */
 void conf_free(Conf *conf);
@@ -1582,6 +1727,8 @@ void load_open_settings(settings_r *sesskey, Conf *conf);
 void get_sesslist(struct sesslist *, bool allocate);
 bool do_defaults(const char *, Conf *);
 void registry_cleanup(void);
+void settings_set_default_protocol(int);
+void settings_set_default_port(int);
 
 /*
  * Functions used by settings.c to provide platform-specific
@@ -1627,6 +1774,7 @@ void term_blink(Terminal *, bool set_cursor);
 void term_do_paste(Terminal *, const wchar_t *, int);
 void term_nopaste(Terminal *);
 void term_copyall(Terminal *, const int *, int);
+void term_pre_reconfig(Terminal *, Conf *);
 void term_reconfig(Terminal *, Conf *);
 void term_request_copy(Terminal *, const int *clipboards, int n_clipboards);
 void term_request_paste(Terminal *, int clipboard);
@@ -1640,6 +1788,13 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input);
 void term_set_trust_status(Terminal *term, bool trusted);
 void term_keyinput(Terminal *, int codepage, const void *buf, int len);
 void term_keyinputw(Terminal *, const wchar_t * widebuf, int len);
+void term_get_cursor_position(Terminal *term, int *x, int *y);
+void term_setup_window_titles(Terminal *term, const char *title_hostname);
+void term_notify_minimised(Terminal *term, bool minimised);
+void term_notify_palette_overrides_changed(Terminal *term);
+void term_notify_window_pos(Terminal *term, int x, int y);
+void term_notify_window_size_pixels(Terminal *term, int x, int y);
+void term_palette_override(Terminal *term, unsigned osc4_index, rgb rgb);
 
 typedef enum SmallKeypadKey {
     SKK_HOME, SKK_END, SKK_INSERT, SKK_DELETE, SKK_PGUP, SKK_PGDN,
@@ -1689,6 +1844,11 @@ struct LogPolicyVtable {
      * file :-)
      */
     void (*logging_error)(LogPolicy *lp, const char *event);
+
+    /*
+     * Ask whether extra verbose log messages are required.
+     */
+    bool (*verbose)(LogPolicy *lp);
 };
 struct LogPolicy {
     const LogPolicyVtable *vt;
@@ -1702,6 +1862,19 @@ static inline int lp_askappend(
 { return lp->vt->askappend(lp, filename, callback, ctx); }
 static inline void lp_logging_error(LogPolicy *lp, const char *event)
 { lp->vt->logging_error(lp, event); }
+static inline bool lp_verbose(LogPolicy *lp)
+{ return lp->vt->verbose(lp); }
+
+/* Defined in conscli.c, used in several console command-line tools */
+extern LogPolicy console_cli_logpolicy[];
+
+int console_askappend(LogPolicy *lp, Filename *filename,
+                      void (*callback)(void *ctx, int result), void *ctx);
+void console_logging_error(LogPolicy *lp, const char *string);
+void console_eventlog(LogPolicy *lp, const char *string);
+bool null_lp_verbose_yes(LogPolicy *lp);
+bool null_lp_verbose_no(LogPolicy *lp);
+bool cmdline_lp_verbose(LogPolicy *lp);
 
 LogContext *log_init(LogPolicy *lp, Conf *conf);
 void log_free(LogContext *logctx);
@@ -1733,10 +1906,6 @@ void log_packet(LogContext *logctx, int direction, int type,
                 const unsigned long *sequence,
                 unsigned downstream_id, const char *additional_log_text);
 
-/* This is defined by applications that have an obvious logging
- * destination like standard error or the GUI. */
-extern LogPolicy default_logpolicy[1];
-
 /*
  * Exports from testback.c
  */
@@ -1766,6 +1935,12 @@ extern const struct BackendVtable telnet_backend;
  * Exports from ssh.c.
  */
 extern const struct BackendVtable ssh_backend;
+extern const struct BackendVtable sshconn_backend;
+
+/*
+ * Exports from supdup.c.
+ */
+extern const struct BackendVtable supdup_backend;
 
 /*
  * Exports from ldisc.c.
@@ -1796,9 +1971,14 @@ void random_unref(void);
  * logical main() no matter whether it needed random numbers or
  * not. */
 void random_clear(void);
-/* random_setup_special is used by PuTTYgen. It makes an extra-big
- * random number generator. */
-void random_setup_special();
+/* random_setup_custom sets up the process-global random number
+ * generator specially, with a hash function of your choice. */
+void random_setup_custom(const ssh_hashalg *hash);
+/* random_setup_special() is a macro wrapper on that, which makes an
+ * extra-big one based on the largest hash function we have. It's
+ * defined this way to avoid what would otherwise be an unnecessary
+ * module dependency from sshrand.c to a hash function implementation. */
+#define random_setup_special() random_setup_custom(&ssh_shake256_114bytes)
 /* Manually drop a random seed into the random number generator, e.g.
  * just before generating a key. */
 void random_reseed(ptrlen seed);
@@ -1897,6 +2077,9 @@ void agent_cancel_query(agent_pending_query *);
 void agent_query_synchronous(strbuf *in, void **out, int *outlen);
 bool agent_exists(void);
 
+/* For stream-oriented agent connections, if available. */
+Socket *agent_connect(Plug *plug);
+
 /*
  * Exports from wildcard.c
  */
@@ -1956,14 +2139,36 @@ void cmdline_run_saved(Conf *);
 void cmdline_cleanup(void);
 int cmdline_get_passwd_input(prompts_t *p);
 bool cmdline_host_ok(Conf *);
-#define TOOLTYPE_FILETRANSFER 1
-#define TOOLTYPE_NONNETWORK 2
-#define TOOLTYPE_HOST_ARG 4
-#define TOOLTYPE_HOST_ARG_CAN_BE_SESSION 8
-#define TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX 16
-#define TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD 32
-#define TOOLTYPE_PORT_ARG 64
-extern int cmdline_tooltype;
+bool cmdline_verbose(void);
+bool cmdline_loaded_session(void);
+
+/*
+ * Here we have a flags word provided by each tool, which describes
+ * the capabilities of that tool that cmdline.c needs to know about.
+ * It will refuse certain command-line options if a particular tool
+ * inherently can't do anything sensible. For example, the file
+ * transfer tools (psftp, pscp) can't do a great deal with protocol
+ * selections (ever tried running scp over telnet?) or with port
+ * forwarding (even if it wasn't a hideously bad idea, they don't have
+ * the select/poll infrastructure to make them work).
+ */
+extern const unsigned cmdline_tooltype;
+
+/* Bit flags for the above */
+#define TOOLTYPE_LIST(X)                        \
+    X(TOOLTYPE_FILETRANSFER)                    \
+    X(TOOLTYPE_NONNETWORK)                      \
+    X(TOOLTYPE_HOST_ARG)                        \
+    X(TOOLTYPE_HOST_ARG_CAN_BE_SESSION)         \
+    X(TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX)        \
+    X(TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD)   \
+    X(TOOLTYPE_PORT_ARG)                        \
+    X(TOOLTYPE_NO_VERBOSE_OPTION)               \
+    /* end of list */
+#define BITFLAG_INDEX(val) val ## _bitflag_index,
+enum { TOOLTYPE_LIST(BITFLAG_INDEX) };
+#define BITFLAG_DEF(val) val = 1U << (val ## _bitflag_index),
+enum { TOOLTYPE_LIST(BITFLAG_DEF) };
 
 void cmdline_error(const char *, ...) PRINTF_LIKE(1, 2);
 
@@ -1983,8 +2188,6 @@ void conf_filesel_handler(union control *ctrl, dlgparam *dlg,
                           void *data, int event);
 void conf_fontsel_handler(union control *ctrl, dlgparam *dlg,
                           void *data, int event);
-/* Much more special-purpose function needed by sercfg.c */
-void config_protocolbuttons_handler(union control *, dlgparam *, void *, int);
 
 void setup_config_box(struct controlbox *b, bool midsession,
                       int protocol, int protcfginfo);

+ 28 - 8
source/putty/settings.c

@@ -39,6 +39,7 @@ static const struct keyvalwhere kexnames[] = {
 
 static const struct keyvalwhere hknames[] = {
     { "ed25519",    HK_ED25519,             -1, +1 },
+    { "ed448",      HK_ED448,               -1, +1 },
     { "ecdsa",      HK_ECDSA,               -1, -1 },
     { "dsa",        HK_DSA,                 -1, -1 },
     { "rsa",        HK_RSA,                 -1, -1 },
@@ -69,6 +70,10 @@ const char *const ttymodes[] = {
     "CS7",      "CS8",      "PARENB",   "PARODD",   NULL
 };
 
+static int default_protocol, default_port;
+void settings_set_default_protocol(int newval) { default_protocol = newval; }
+void settings_set_default_port(int newval) { default_port = newval; }
+
 /*
  * Convenience functions to access the backends[] array
  * (which is only present in tools that manage settings).
@@ -78,7 +83,7 @@ const struct BackendVtable *backend_vt_from_name(const char *name)
 {
     const struct BackendVtable *const *p;
     for (p = backends; *p != NULL; p++)
-        if (!strcmp((*p)->name, name))
+        if (!strcmp((*p)->id, name))
             return *p;
     return NULL;
 }
@@ -488,13 +493,12 @@ static void write_clip_setting(settings_w *sesskey, const char *savekey,
       case CLIPUI_EXPLICIT:
         write_setting_s(sesskey, savekey, "explicit");
         break;
-      case CLIPUI_CUSTOM:
-        {
-            char *sval = dupcat("custom:", conf_get_str(conf, strconfkey));
-            write_setting_s(sesskey, savekey, sval);
-            sfree(sval);
-        }
+      case CLIPUI_CUSTOM: {
+        char *sval = dupcat("custom:", conf_get_str(conf, strconfkey));
+        write_setting_s(sesskey, savekey, sval);
+        sfree(sval);
         break;
+      }
     }
 }
 
@@ -553,7 +557,7 @@ void save_open_settings(settings_w *sesskey, Conf *conf)
         const struct BackendVtable *vt =
             backend_vt_from_proto(conf_get_int(conf, CONF_protocol));
         if (vt)
-            p = vt->name;
+            p = vt->id;
     }
     write_setting_s(sesskey, "Protocol", p);
     write_setting_i(sesskey, "PortNumber", conf_get_int(conf, CONF_port));
@@ -783,6 +787,14 @@ void save_open_settings(settings_w *sesskey, Conf *conf)
     write_setting_b(sesskey, "ConnectionSharingUpstream", conf_get_bool(conf, CONF_ssh_connection_sharing_upstream));
     write_setting_b(sesskey, "ConnectionSharingDownstream", conf_get_bool(conf, CONF_ssh_connection_sharing_downstream));
     wmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys, false);
+
+    /*
+     * SUPDUP settings
+     */
+    write_setting_s(sesskey, "SUPDUPLocation", conf_get_str(conf, CONF_supdup_location));
+    write_setting_i(sesskey, "SUPDUPCharset", conf_get_int(conf, CONF_supdup_ascii_set));
+    write_setting_b(sesskey, "SUPDUPMoreProcessing", conf_get_bool(conf, CONF_supdup_more));
+    write_setting_b(sesskey, "SUPDUPScrolling", conf_get_bool(conf, CONF_supdup_scroll));
 }
 
 bool load_settings(const char *section, Conf *conf)
@@ -1255,6 +1267,14 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
     gppb(sesskey, "ConnectionSharingDownstream", true,
          conf, CONF_ssh_connection_sharing_downstream);
     gppmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys);
+
+    /*
+     * SUPDUP settings
+     */
+    gpps(sesskey, "SUPDUPLocation", "The Internet", conf, CONF_supdup_location);
+    gppi(sesskey, "SUPDUPCharset", false, conf, CONF_supdup_ascii_set);
+    gppb(sesskey, "SUPDUPMoreProcessing", false, conf, CONF_supdup_more);
+    gppb(sesskey, "SUPDUPScrolling", false, conf, CONF_supdup_scroll);
 }
 
 bool do_defaults(const char *session, Conf *conf)

+ 104 - 51
source/putty/ssh.c

@@ -313,8 +313,8 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv,
         ssh_connect_bpp(ssh);
 
         connection_layer = ssh2_connection_new(
-            ssh, NULL, false, ssh->conf, ssh_verstring_get_remote(old_bpp),
-            &ssh->cl);
+            ssh, ssh->connshare, false, ssh->conf,
+            ssh_verstring_get_remote(old_bpp), &ssh->cl);
         ssh_connect_ppl(ssh, connection_layer);
         ssh->base_layer = connection_layer;
     }
@@ -461,7 +461,8 @@ static void ssh_initiate_connection_close(Ssh *ssh)
     va_list ap;                                 \
     va_start(ap, fmt);                          \
     msg = dupvprintf(fmt, ap);                  \
-    va_end(ap);
+    va_end(ap);                                 \
+    ((void)0) /* eat trailing semicolon */
 
 void ssh_remote_error(Ssh *ssh, const char *fmt, ...)
 {
@@ -561,7 +562,7 @@ void ssh_user_close(Ssh *ssh, const char *fmt, ...)
     }
 }
 
-void ssh_deferred_abort_callback(void *vctx)
+static void ssh_deferred_abort_callback(void *vctx)
 {
     Ssh *ssh = (Ssh *)vctx;
     char *msg = ssh->deferred_abort_message;
@@ -579,8 +580,8 @@ void ssh_sw_abort_deferred(Ssh *ssh, const char *fmt, ...)
     }
 }
 
-static void ssh_socket_log(Plug *plug, int type, SockAddr *addr, int port,
-                           const char *error_msg, int error_code)
+static void ssh_socket_log(Plug *plug, PlugLogType type, SockAddr *addr,
+                           int port, const char *error_msg, int error_code)
 {
     Ssh *ssh = container_of(plug, Ssh, plug);
 
@@ -694,12 +695,24 @@ static bool ssh_test_for_upstream(const char *host, int port, Conf *conf)
     return ret;
 }
 
+static char *ssh_close_warn_text(Backend *be)
+{
+    Ssh *ssh = container_of(be, Ssh, backend);
+    if (!ssh->connshare)
+        return NULL;
+    int ndowns = share_ndownstreams(ssh->connshare);
+    if (ndowns == 0)
+        return NULL;
+    char *msg = dupprintf("This will also close %d downstream connection%s.",
+                          ndowns, ndowns==1 ? "" : "s");
+    return msg;
+}
+
 static const PlugVtable Ssh_plugvt = {
-    ssh_socket_log,
-    ssh_closing,
-    ssh_receive,
-    ssh_sent,
-    NULL
+    .log = ssh_socket_log,
+    .closing = ssh_closing,
+    .receive = ssh_receive,
+    .sent = ssh_sent,
 };
 
 /*
@@ -708,7 +721,7 @@ static const PlugVtable Ssh_plugvt = {
  * Also places the canonical host name into `realhost'. It must be
  * freed by the caller.
  */
-static const char *connect_to_host(
+static char *connect_to_host(
     Ssh *ssh, const char *host, int port, char **realhost,
     bool nodelay, bool keepalive)
 {
@@ -752,7 +765,7 @@ static const char *connect_to_host(
         ssh->fullhostname = NULL;
         *realhost = dupstr(host);      /* best we can do */
 
-        if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) {
+        if (seat_verbose(ssh->seat) || seat_interactive(ssh->seat)) {
             /* In an interactive session, or in verbose mode, announce
              * in the console window that we're a sharing downstream,
              * to avoid confusing users as to why this session doesn't
@@ -774,7 +787,7 @@ static const char *connect_to_host(
                            ssh->logctx, "SSH connection");
         if ((err = sk_addr_error(addr)) != NULL) {
             sk_addr_free(addr);
-            return err;
+            return dupstr(err);
         }
         ssh->fullhostname = dupstr(*realhost);   /* save in case of GSSAPI */
 
@@ -784,7 +797,7 @@ static const char *connect_to_host(
         if ((err = sk_socket_error(ssh->s)) != NULL) {
             ssh->s = NULL;
             seat_notify_remote_exit(ssh->seat);
-            return err;
+            return dupstr(err);
         }
     }
 
@@ -869,17 +882,28 @@ static void ssh_cache_conf_values(Ssh *ssh)
     ssh->pls.omit_data = conf_get_bool(ssh->conf, CONF_logomitdata);
 }
 
+bool ssh_is_bare(Ssh *ssh)
+{
+    return ssh->backend.vt->protocol == PROT_SSHCONN;
+}
+
+/* Dummy connlayer must provide ssh_sharing_no_more_downstreams,
+ * because it might be called early due to plink -shareexists */
+static void dummy_sharing_no_more_downstreams(ConnectionLayer *cl) {}
+static const ConnectionLayerVtable dummy_connlayer_vtable = {
+    .sharing_no_more_downstreams = dummy_sharing_no_more_downstreams,
+};
+
 /*
  * Called to set up the connection.
  *
  * Returns an error message, or NULL on success.
  */
-static const char *ssh_init(Seat *seat, Backend **backend_handle,
-                            LogContext *logctx, Conf *conf,
-                            const char *host, int port, char **realhost,
-                            bool nodelay, bool keepalive)
+static char *ssh_init(const BackendVtable *vt, Seat *seat,
+                      Backend **backend_handle, LogContext *logctx,
+                      Conf *conf, const char *host, int port,
+                      char **realhost, bool nodelay, bool keepalive)
 {
-    const char *p;
     Ssh *ssh;
 
     ssh = snew(Ssh);
@@ -900,24 +924,28 @@ static const char *ssh_init(Seat *seat, Backend **backend_handle,
     ssh->term_width = conf_get_int(ssh->conf, CONF_width);
     ssh->term_height = conf_get_int(ssh->conf, CONF_height);
 
-    ssh->backend.vt = &ssh_backend;
+    ssh->backend.vt = vt;
     *backend_handle = &ssh->backend;
 
+    ssh->bare_connection = (vt->protocol == PROT_SSHCONN);
+
     ssh->seat = seat;
+    ssh->cl_dummy.vt = &dummy_connlayer_vtable;
     ssh->cl_dummy.logctx = ssh->logctx = logctx;
 
     random_ref(); /* do this now - may be needed by sharing setup code */
     ssh->need_random_unref = true;
 
-    p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive);
-    if (p != NULL) {
+    char *conn_err = connect_to_host(
+        ssh, host, port, realhost, nodelay, keepalive);
+    if (conn_err) {
         /* Call random_unref now instead of waiting until the caller
          * frees this useless Ssh object, in case the caller is
          * impatient and just exits without bothering, in which case
          * the random seed won't be re-saved. */
         ssh->need_random_unref = false;
         random_unref();
-        return p;
+        return conn_err;
     }
 
     return NULL;
@@ -1067,21 +1095,21 @@ static const SessionSpecial *ssh_get_specials(Backend *be)
      * and amalgamate the list into one combined one.
      */
 
-    struct ssh_add_special_ctx ctx;
+    struct ssh_add_special_ctx ctx[1];
 
-    ctx.specials = NULL;
-    ctx.nspecials = ctx.specials_size = 0;
+    ctx->specials = NULL;
+    ctx->nspecials = ctx->specials_size = 0;
 
     if (ssh->base_layer)
-        ssh_ppl_get_specials(ssh->base_layer, ssh_add_special, &ctx);
+        ssh_ppl_get_specials(ssh->base_layer, ssh_add_special, ctx);
 
-    if (ctx.specials) {
+    if (ctx->specials) {
         /* If the list is non-empty, terminate it with a SS_EXITMENU. */
-        ssh_add_special(&ctx, NULL, SS_EXITMENU, 0);
+        ssh_add_special(ctx, NULL, SS_EXITMENU, 0);
     }
 
     sfree(ssh->specials);
-    ssh->specials = ctx.specials;
+    ssh->specials = ctx->specials;
     return ssh->specials;
 }
 
@@ -1186,26 +1214,51 @@ void ssh_got_fallback_cmd(Ssh *ssh)
     ssh->fallback_cmd = true;
 }
 
-const struct BackendVtable ssh_backend = {
-    ssh_init,
-    ssh_free,
-    ssh_reconfig,
-    ssh_send,
-    ssh_sendbuffer,
-    ssh_size,
-    ssh_special,
-    ssh_get_specials,
-    ssh_connected,
-    ssh_return_exitcode,
-    ssh_sendok,
-    ssh_ldisc,
-    ssh_provide_ldisc,
-    ssh_unthrottle,
-    ssh_cfg_info,
-    ssh_test_for_upstream,
-    "ssh",
-    PROT_SSH,
-    22
+const BackendVtable ssh_backend = {
+    .init = ssh_init,
+    .free = ssh_free,
+    .reconfig = ssh_reconfig,
+    .send = ssh_send,
+    .sendbuffer = ssh_sendbuffer,
+    .size = ssh_size,
+    .special = ssh_special,
+    .get_specials = ssh_get_specials,
+    .connected = ssh_connected,
+    .exitcode = ssh_return_exitcode,
+    .sendok = ssh_sendok,
+    .ldisc_option_state = ssh_ldisc,
+    .provide_ldisc = ssh_provide_ldisc,
+    .unthrottle = ssh_unthrottle,
+    .cfg_info = ssh_cfg_info,
+    .test_for_upstream = ssh_test_for_upstream,
+    .close_warn_text = ssh_close_warn_text,
+    .id = "ssh",
+    .displayname = "SSH",
+    .protocol = PROT_SSH,
+    .default_port = 22,
+};
+
+const BackendVtable sshconn_backend = {
+    .init = ssh_init,
+    .free = ssh_free,
+    .reconfig = ssh_reconfig,
+    .send = ssh_send,
+    .sendbuffer = ssh_sendbuffer,
+    .size = ssh_size,
+    .special = ssh_special,
+    .get_specials = ssh_get_specials,
+    .connected = ssh_connected,
+    .exitcode = ssh_return_exitcode,
+    .sendok = ssh_sendok,
+    .ldisc_option_state = ssh_ldisc,
+    .provide_ldisc = ssh_provide_ldisc,
+    .unthrottle = ssh_unthrottle,
+    .cfg_info = ssh_cfg_info,
+    .test_for_upstream = ssh_test_for_upstream,
+    .close_warn_text = ssh_close_warn_text,
+    .id = "ssh-connection",
+    .displayname = "Bare ssh-connection",
+    .protocol = PROT_SSHCONN,
 };
 
 #ifdef MPEXT

+ 193 - 69
source/putty/ssh.h

@@ -215,6 +215,8 @@ struct ssh_rportfwd {
 };
 void free_rportfwd(struct ssh_rportfwd *rpf);
 
+typedef struct ConnectionLayerVtable ConnectionLayerVtable;
+
 struct ConnectionLayerVtable {
     /* Allocate and free remote-to-local port forwardings, called by
      * PortFwdManager or by connection sharing */
@@ -298,11 +300,10 @@ struct ConnectionLayerVtable {
      * channel) what its preference for line-discipline options is. */
     void (*set_ldisc_option)(ConnectionLayer *cl, int option, bool value);
 
-    /* Communicate to the connection layer whether X and agent
-     * forwarding were successfully enabled (for purposes of
-     * knowing whether to accept subsequent channel-opens). */
+    /* Communicate to the connection layer whether X forwarding was
+     * successfully enabled (for purposes of knowing whether to accept
+     * subsequent channel-opens). */
     void (*enable_x_fwd)(ConnectionLayer *cl);
-    void (*enable_agent_fwd)(ConnectionLayer *cl);
 
     /* Communicate to the connection layer whether the main session
      * channel currently wants user input. */
@@ -374,8 +375,6 @@ static inline void ssh_set_ldisc_option(ConnectionLayer *cl, int opt, bool val)
 { cl->vt->set_ldisc_option(cl, opt, val); }
 static inline void ssh_enable_x_fwd(ConnectionLayer *cl)
 { cl->vt->enable_x_fwd(cl); }
-static inline void ssh_enable_agent_fwd(ConnectionLayer *cl)
-{ cl->vt->enable_agent_fwd(cl); }
 static inline void ssh_set_wants_user_input(ConnectionLayer *cl, bool wanted)
 { cl->vt->set_wants_user_input(cl, wanted); }
 
@@ -391,7 +390,7 @@ char *portfwdmgr_connect(PortFwdManager *mgr, Channel **chan_ret,
 bool portfwdmgr_listen(PortFwdManager *mgr, const char *host, int port,
                        const char *keyhost, int keyport, Conf *conf);
 bool portfwdmgr_unlisten(PortFwdManager *mgr, const char *host, int port);
-Channel *portfwd_raw_new(ConnectionLayer *cl, Plug **plug);
+Channel *portfwd_raw_new(ConnectionLayer *cl, Plug **plug, bool start_ready);
 void portfwd_raw_free(Channel *pfchan);
 void portfwd_raw_setup(Channel *pfchan, Socket *s, SshChannel *sc);
 
@@ -405,6 +404,7 @@ void ssh_throttle_conn(Ssh *ssh, int adjust);
 void ssh_got_exitcode(Ssh *ssh, int status);
 void ssh_ldisc_update(Ssh *ssh);
 void ssh_got_fallback_cmd(Ssh *ssh);
+bool ssh_is_bare(Ssh *ssh);
 
 /* Communications back to ssh.c from the BPP */
 void ssh_conn_processed_data(Ssh *ssh);
@@ -481,6 +481,7 @@ struct ec_ecurve
     EdwardsCurve *ec;
     EdwardsPoint *G;
     mp_int *G_order;
+    unsigned log2_cofactor;
 };
 
 typedef enum EllipticCurveType {
@@ -510,6 +511,7 @@ const ssh_keyalg *ec_alg_by_oid(int len, const void *oid,
                                         const struct ec_curve **curve);
 const unsigned char *ec_alg_oid(const ssh_keyalg *alg, int *oidlen);
 extern const int ec_nist_curve_lengths[], n_ec_nist_curve_lengths;
+extern const int ec_ed_curve_lengths[], n_ec_ed_curve_lengths;
 bool ec_nist_alg_and_curve_by_bits(int bits,
                                    const struct ec_curve **curve,
                                    const ssh_keyalg **alg);
@@ -533,6 +535,24 @@ struct eddsa_key {
 WeierstrassPoint *ecdsa_public(mp_int *private_key, const ssh_keyalg *alg);
 EdwardsPoint *eddsa_public(mp_int *private_key, const ssh_keyalg *alg);
 
+typedef struct key_components {
+    size_t ncomponents, componentsize;
+    struct {
+        char *name;
+        bool is_mp_int;
+        union {
+            char *text;
+            mp_int *mp;
+        };
+    } *components;
+} key_components;
+key_components *key_components_new(void);
+void key_components_add_text(key_components *kc,
+                             const char *name, const char *value);
+void key_components_add_mp(key_components *kc,
+                           const char *name, mp_int *value);
+void key_components_free(key_components *kc);
+
 /*
  * SSH-1 never quite decided which order to store the two components
  * of an RSA key. During connection setup, the server sends its host
@@ -553,11 +573,14 @@ mp_int *rsa_ssh1_decrypt(mp_int *input, RSAKey *key);
 bool rsa_ssh1_decrypt_pkcs1(mp_int *input, RSAKey *key, strbuf *outbuf);
 char *rsastr_fmt(RSAKey *key);
 char *rsa_ssh1_fingerprint(RSAKey *key);
+char **rsa_ssh1_fake_all_fingerprints(RSAKey *key);
 bool rsa_verify(RSAKey *key);
 void rsa_ssh1_public_blob(BinarySink *bs, RSAKey *key, RsaSsh1Order order);
 int rsa_ssh1_public_blob_len(ptrlen data);
+void rsa_ssh1_private_blob_agent(BinarySink *bs, RSAKey *key);
 void freersapriv(RSAKey *key);
 void freersakey(RSAKey *key);
+key_components *rsa_components(RSAKey *key);
 #endif // WINSCP_VS
 
 #ifndef WINSCP_VS
@@ -744,32 +767,52 @@ struct ssh_hash {
 
 struct ssh_hashalg {
     ssh_hash *(*new)(const ssh_hashalg *alg);
-    ssh_hash *(*copy)(ssh_hash *);
-    void (*final)(ssh_hash *, unsigned char *); /* ALSO FREES THE ssh_hash! */
+    void (*reset)(ssh_hash *);
+    void (*copyfrom)(ssh_hash *dest, ssh_hash *src);
+    void (*digest)(ssh_hash *, unsigned char *);
     void (*free)(ssh_hash *);
-    int hlen; /* output length in bytes */
-    int blocklen; /* length of the hash's input block, or 0 for N/A */
+    size_t hlen; /* output length in bytes */
+    size_t blocklen; /* length of the hash's input block, or 0 for N/A */
     const char *text_basename;     /* the semantic name of the hash */
     const char *annotation;   /* extra info, e.g. which of multiple impls */
     const char *text_name;    /* both combined, e.g. "SHA-n (unaccelerated)" */
+    const void *extra;        /* private to the hash implementation */
 };
 
 static inline ssh_hash *ssh_hash_new(const ssh_hashalg *alg)
-{ return alg->new(alg); }
-static inline ssh_hash *ssh_hash_copy(ssh_hash *h)
-{ return h->vt->copy(h); }
-static inline void ssh_hash_final(ssh_hash *h, unsigned char *out)
-{ h->vt->final(h, out); }
+{ ssh_hash *h = alg->new(alg); if (h) h->vt->reset(h); return h; }
+static inline ssh_hash *ssh_hash_copy(ssh_hash *orig)
+{ ssh_hash *h = orig->vt->new(orig->vt); h->vt->copyfrom(h, orig); return h; }
+static inline void ssh_hash_digest(ssh_hash *h, unsigned char *out)
+{ h->vt->digest(h, out); }
 static inline void ssh_hash_free(ssh_hash *h)
 { h->vt->free(h); }
 static inline const ssh_hashalg *ssh_hash_alg(ssh_hash *h)
 { return h->vt; }
 
+/* The reset and copyfrom vtable methods return void. But for call-site
+ * convenience, these wrappers return their input pointer. */
+static inline ssh_hash *ssh_hash_reset(ssh_hash *h)
+{ h->vt->reset(h); return h; }
+static inline ssh_hash *ssh_hash_copyfrom(ssh_hash *dest, ssh_hash *src)
+{ dest->vt->copyfrom(dest, src); return dest; }
+
+/* ssh_hash_final emits the digest _and_ frees the ssh_hash */
+static inline void ssh_hash_final(ssh_hash *h, unsigned char *out)
+{ h->vt->digest(h, out); h->vt->free(h); }
+
+/* ssh_hash_digest_nondestructive generates a finalised hash from the
+ * given object without changing its state, so you can continue
+ * appending data to get a hash of an extended string. */
+static inline void ssh_hash_digest_nondestructive(ssh_hash *h,
+                                                  unsigned char *out)
+{ ssh_hash_final(ssh_hash_copy(h), out); }
+
 /* Handy macros for defining all those text-name fields at once */
 #define HASHALG_NAMES_BARE(base) \
-    base, NULL, base
-#define HASHALG_NAMES_ANNOTATED(base, annotation) \
-    base, annotation, base " (" annotation ")"
+    .text_basename = base, .annotation = NULL, .text_name = base
+#define HASHALG_NAMES_ANNOTATED(base, ann) \
+    .text_basename = base, .annotation = ann, .text_name = base " (" ann ")"
 
 #ifndef WINSCP_VS
 
@@ -809,6 +852,7 @@ struct ssh_keyalg {
     void (*private_blob)(ssh_key *key, BinarySink *);
     void (*openssh_blob) (ssh_key *key, BinarySink *);
     char *(*cache_str) (ssh_key *key);
+    key_components *(*components) (ssh_key *key);
 
     /* 'Class methods' that don't deal with an ssh_key at all */
     int (*pubkey_bits) (const ssh_keyalg *self, ptrlen blob);
@@ -845,6 +889,8 @@ static inline void ssh_key_openssh_blob(ssh_key *key, BinarySink *bs)
 { key->vt->openssh_blob(key, bs); }
 static inline char *ssh_key_cache_str(ssh_key *key)
 { return key->vt->cache_str(key); }
+static inline key_components *ssh_key_components(ssh_key *key)
+{ return key->vt->components(key); }
 static inline int ssh_key_public_bits(const ssh_keyalg *self, ptrlen blob)
 { return self->pubkey_bits(self, blob); }
 static inline const ssh_keyalg *ssh_key_alg(ssh_key *key)
@@ -914,8 +960,20 @@ struct ssh2_userkey {
     char *comment;                     /* the key comment */
 };
 
+/* Argon2 password hashing function */
+typedef enum { Argon2d = 0, Argon2i = 1, Argon2id = 2 } Argon2Flavour;
+void argon2(Argon2Flavour, uint32_t mem, uint32_t passes,
+            uint32_t parallel, uint32_t taglen,
+            ptrlen P, ptrlen S, ptrlen K, ptrlen X, strbuf *out);
+void argon2_choose_passes(
+    Argon2Flavour, uint32_t mem, uint32_t milliseconds, uint32_t *passes,
+    uint32_t parallel, uint32_t taglen, ptrlen P, ptrlen S, ptrlen K, ptrlen X,
+    strbuf *out);
+/* The H' hash defined in Argon2, exposed just for testcrypt */
+strbuf *argon2_long_hash(unsigned length, ptrlen data);
+
 /* The maximum length of any hash algorithm. (bytes) */
-#define MAX_HASH_LEN (64)              /* longest is SHA-512 */
+#define MAX_HASH_LEN (114) /* longest is SHAKE256 with 114-byte output */
 
 extern const ssh_cipheralg ssh_3des_ssh1;
 extern const ssh_cipheralg ssh_blowfish_ssh1;
@@ -960,20 +1018,34 @@ extern const ssh_hashalg ssh_sha256;
 extern const ssh_hashalg ssh_sha256_hw;
 extern const ssh_hashalg ssh_sha256_sw;
 extern const ssh_hashalg ssh_sha384;
+extern const ssh_hashalg ssh_sha384_hw;
+extern const ssh_hashalg ssh_sha384_sw;
 extern const ssh_hashalg ssh_sha512;
+extern const ssh_hashalg ssh_sha512_hw;
+extern const ssh_hashalg ssh_sha512_sw;
+extern const ssh_hashalg ssh_sha3_224;
+extern const ssh_hashalg ssh_sha3_256;
+extern const ssh_hashalg ssh_sha3_384;
+extern const ssh_hashalg ssh_sha3_512;
+extern const ssh_hashalg ssh_shake256_114bytes;
+extern const ssh_hashalg ssh_blake2b;
 extern const ssh_kexes ssh_diffiehellman_group1;
 extern const ssh_kexes ssh_diffiehellman_group14;
 extern const ssh_kexes ssh_diffiehellman_gex;
 extern const ssh_kexes ssh_gssk5_sha1_kex;
 extern const ssh_kexes ssh_rsa_kex;
 extern const ssh_kex ssh_ec_kex_curve25519;
+extern const ssh_kex ssh_ec_kex_curve448;
 extern const ssh_kex ssh_ec_kex_nistp256;
 extern const ssh_kex ssh_ec_kex_nistp384;
 extern const ssh_kex ssh_ec_kex_nistp521;
 extern const ssh_kexes ssh_ecdh_kex;
 extern const ssh_keyalg ssh_dss;
 extern const ssh_keyalg ssh_rsa;
+extern const ssh_keyalg ssh_rsa_sha256;
+extern const ssh_keyalg ssh_rsa_sha512;
 extern const ssh_keyalg ssh_ecdsa_ed25519;
+extern const ssh_keyalg ssh_ecdsa_ed448;
 extern const ssh_keyalg ssh_ecdsa_nistp256;
 extern const ssh_keyalg ssh_ecdsa_nistp384;
 extern const ssh_keyalg ssh_ecdsa_nistp521;
@@ -986,6 +1058,10 @@ extern const ssh2_macalg ssh_hmac_sha256;
 extern const ssh2_macalg ssh2_poly1305;
 extern const ssh_compression_alg ssh_zlib;
 
+/* Special constructor: BLAKE2b can be instantiated with any hash
+ * length up to 128 bytes */
+ssh_hash *blake2b_new_general(unsigned hashlen);
+
 /*
  * On some systems, you have to detect hardware crypto acceleration by
  * asking the local OS API rather than OS-agnostically asking the CPU
@@ -995,6 +1071,7 @@ extern const ssh_compression_alg ssh_zlib;
 bool platform_aes_hw_available(void);
 bool platform_sha256_hw_available(void);
 bool platform_sha1_hw_available(void);
+bool platform_sha512_hw_available(void);
 
 /*
  * PuTTY version number formatted as an SSH version string.
@@ -1159,13 +1236,6 @@ mp_int *dh_create_e(dh_ctx *, int nbits);
 const char *dh_validate_f(dh_ctx *, mp_int *f);
 mp_int *dh_find_K(dh_ctx *, mp_int *f);
 
-bool rsa_ssh1_encrypted(const Filename *filename, char **comment);
-int rsa_ssh1_loadpub(const Filename *filename, BinarySink *bs,
-                     char **commentptr, const char **errorstr);
-int rsa_ssh1_loadkey(const Filename *filename, RSAKey *key,
-                     const char *passphrase, const char **errorstr);
-bool rsa_ssh1_savekey(const Filename *filename, RSAKey *key, char *passphrase);
-
 static inline bool is_base64_char(char c)
 {
     return ((c >= '0' && c <= '9') ||
@@ -1180,21 +1250,75 @@ extern void base64_encode_atom(const unsigned char *data, int n, char *out);
 extern void base64_encode(FILE *fp, const unsigned char *data, int datalen,
                           int cpl);
 
-/* ssh2_load_userkey can return this as an error */
+/* ppk_load_* can return this as an error */
 extern ssh2_userkey ssh2_wrong_passphrase;
 #define SSH2_WRONG_PASSPHRASE (&ssh2_wrong_passphrase)
 
-bool ssh2_userkey_encrypted(const Filename *filename, char **comment);
-ssh2_userkey *ssh2_load_userkey(
-    const Filename *filename, const char *passphrase, const char **errorstr);
-bool ssh2_userkey_loadpub(
-    const Filename *filename, char **algorithm, BinarySink *bs,
-    char **commentptr, const char **errorstr);
-bool ssh2_save_userkey(
-    const Filename *filename, ssh2_userkey *key, char *passphrase);
+bool ppk_encrypted_s(BinarySource *src, char **comment);
+bool ppk_encrypted_f(const Filename *filename, char **comment);
+bool rsa1_encrypted_s(BinarySource *src, char **comment);
+bool rsa1_encrypted_f(const Filename *filename, char **comment);
+
+ssh2_userkey *ppk_load_s(BinarySource *src, const char *passphrase,
+                         const char **errorstr);
+ssh2_userkey *ppk_load_f(const Filename *filename, const char *passphrase,
+                         const char **errorstr);
+int rsa1_load_s(BinarySource *src, RSAKey *key,
+                const char *passphrase, const char **errorstr);
+int rsa1_load_f(const Filename *filename, RSAKey *key,
+                const char *passphrase, const char **errorstr);
+
+typedef struct ppk_save_parameters {
+    unsigned fmt_version;              /* currently 2 or 3 */
+
+    /*
+     * Parameters for fmt_version == 3
+     */
+    Argon2Flavour argon2_flavour;
+    uint32_t argon2_mem;               /* in Kbyte */
+    bool argon2_passes_auto;
+    union {
+        uint32_t argon2_passes;        /* if auto == false */
+        uint32_t argon2_milliseconds;  /* if auto == true */
+    };
+    uint32_t argon2_parallelism;
+
+    /* The ability to choose a specific salt is only intended for the
+     * use of the automated test of PuTTYgen. It's a (mild) security
+     * risk to do it with any passphrase you actually care about,
+     * because it invalidates the entire point of having a salt in the
+     * first place. */
+    const uint8_t *salt;
+    size_t saltlen;
+} ppk_save_parameters;
+extern const ppk_save_parameters ppk_save_default_parameters;
+
+strbuf *ppk_save_sb(ssh2_userkey *key, const char *passphrase,
+                    const ppk_save_parameters *params);
+bool ppk_save_f(const Filename *filename, ssh2_userkey *key,
+                const char *passphrase, const ppk_save_parameters *params);
+strbuf *rsa1_save_sb(RSAKey *key, const char *passphrase);
+bool rsa1_save_f(const Filename *filename, RSAKey *key,
+                 const char *passphrase);
+
+bool ppk_loadpub_s(BinarySource *src, char **algorithm, BinarySink *bs,
+                   char **commentptr, const char **errorstr);
+bool ppk_loadpub_f(const Filename *filename, char **algorithm, BinarySink *bs,
+                   char **commentptr, const char **errorstr);
+int rsa1_loadpub_s(BinarySource *src, BinarySink *bs,
+                   char **commentptr, const char **errorstr);
+int rsa1_loadpub_f(const Filename *filename, BinarySink *bs,
+                   char **commentptr, const char **errorstr);
+
+extern const ssh_keyalg *const all_keyalgs[];
+extern const size_t n_keyalgs;
 const ssh_keyalg *find_pubkey_alg(const char *name);
 const ssh_keyalg *find_pubkey_alg_len(ptrlen name);
 
+/* Convenient wrappers on the LoadedFile mechanism suitable for key files */
+LoadedFile *lf_load_keyfile(const Filename *filename, const char **errptr);
+LoadedFile *lf_load_keyfile_fp(FILE *fp, const char **errptr);
+
 enum {
     SSH_KEYTYPE_UNOPENABLE,
     SSH_KEYTYPE_UNKNOWN,
@@ -1238,27 +1362,47 @@ enum {
     SSH_KEYTYPE_SSH2_PUBLIC_RFC4716,
     SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH
 };
+
+typedef enum {
+    SSH_FPTYPE_MD5,
+    SSH_FPTYPE_SHA256,
+} FingerprintType;
+
+#define SSH_FPTYPE_DEFAULT SSH_FPTYPE_SHA256
+#define SSH_N_FPTYPES (SSH_FPTYPE_SHA256 + 1)
+
+FingerprintType ssh2_pick_fingerprint(char **fingerprints,
+                                      FingerprintType preferred_type);
+FingerprintType ssh2_pick_default_fingerprint(char **fingerprints);
+
 char *ssh1_pubkey_str(RSAKey *ssh1key);
 void ssh1_write_pubkey(FILE *fp, RSAKey *ssh1key);
 char *ssh2_pubkey_openssh_str(ssh2_userkey *key);
 void ssh2_write_pubkey(FILE *fp, const char *comment,
                        const void *v_pub_blob, int pub_len,
                        int keytype);
-char *ssh2_fingerprint_blob(ptrlen);
-char *ssh2_fingerprint(ssh_key *key);
+char *ssh2_fingerprint_blob(ptrlen, FingerprintType);
+char *ssh2_fingerprint(ssh_key *key, FingerprintType);
+char **ssh2_all_fingerprints_for_blob(ptrlen);
+char **ssh2_all_fingerprints(ssh_key *key);
+void ssh2_free_all_fingerprints(char **);
 int key_type(const Filename *filename);
+int key_type_s(BinarySource *src);
 const char *key_type_to_str(int type);
-bool openssh_loadpub_line(char * line, char **algorithm, // WINSCP
-                         BinarySink *bs,
-                         char **commentptr, const char **errorstr);
 
 bool import_possible(int type);
 int import_target_type(int type);
 bool import_encrypted(const Filename *filename, int type, char **comment);
+bool import_encrypted_s(const Filename *filename, BinarySource *src,
+                      int type, char **comment);
 int import_ssh1(const Filename *filename, int type,
                 RSAKey *key, char *passphrase, const char **errmsg_p);
+int import_ssh1_s(BinarySource *src, int type,
+                  RSAKey *key, char *passphrase, const char **errmsg_p);
 ssh2_userkey *import_ssh2(const Filename *filename, int type,
                           char *passphrase, const char **errmsg_p);
+ssh2_userkey *import_ssh2_s(BinarySource *src, int type,
+                          char *passphrase, const char **errmsg_p);
 bool export_ssh1(const Filename *filename, int type,
                  RSAKey *key, char *passphrase);
 bool export_ssh2(const Filename *filename, int type,
@@ -1270,8 +1414,10 @@ void des3_decrypt_pubkey_ossh(const void *key, const void *iv,
                               void *blk, int len);
 void des3_encrypt_pubkey_ossh(const void *key, const void *iv,
                               void *blk, int len);
-void aes256_encrypt_pubkey(const void *key, void *blk, int len);
-void aes256_decrypt_pubkey(const void *key, void *blk, int len);
+void aes256_encrypt_pubkey(const void *key, const void *iv,
+                           void *blk, int len);
+void aes256_decrypt_pubkey(const void *key, const void *iv,
+                           void *blk, int len);
 
 void des_encrypt_xdmauth(const void *key, void *blk, int len);
 void des_decrypt_xdmauth(const void *key, void *blk, int len);
@@ -1280,30 +1426,6 @@ void openssh_bcrypt(const char *passphrase,
                     const unsigned char *salt, int saltbytes,
                     int rounds, unsigned char *out, int outbytes);
 
-/*
- * For progress updates in the key generation utility.
- */
-#define PROGFN_INITIALISE 1
-#define PROGFN_LIN_PHASE 2
-#define PROGFN_EXP_PHASE 3
-#define PROGFN_PHASE_EXTENT 4
-#define PROGFN_READY 5
-#define PROGFN_PROGRESS 6
-typedef void (*progfn_t) (void *param, int action, int phase, int progress);
-
-int rsa_generate(RSAKey *key, int bits, progfn_t pfn,
-                 void *pfnparam);
-int dsa_generate(struct dss_key *key, int bits, progfn_t pfn,
-                 void *pfnparam);
-int ecdsa_generate(struct ecdsa_key *key, int bits, progfn_t pfn,
-                   void *pfnparam);
-int eddsa_generate(struct eddsa_key *key, int bits, progfn_t pfn,
-                   void *pfnparam);
-mp_int *primegen(
-    int bits, int modulus, int residue, mp_int *factor,
-    int phase, progfn_t pfn, void *pfnparam, unsigned firstbits);
-void invent_firstbits(unsigned *one, unsigned *two, unsigned min_separation);
-
 /*
  * Connection-sharing API provided by platforms. This function must
  * either:
@@ -1390,6 +1512,7 @@ void platform_ssh_share_cleanup(const char *name);
     X(y, SSH2_MSG_DEBUG, 4)                                             \
     X(y, SSH2_MSG_SERVICE_REQUEST, 5)                                   \
     X(y, SSH2_MSG_SERVICE_ACCEPT, 6)                                    \
+    X(y, SSH2_MSG_EXT_INFO, 7)                                          \
     X(y, SSH2_MSG_KEXINIT, 20)                                          \
     X(y, SSH2_MSG_NEWKEYS, 21)                                          \
     K(y, SSH2_MSG_KEXDH_INIT, 30, SSH2_PKTCTX_DHGROUP)                  \
@@ -1480,6 +1603,8 @@ enum {
 #define SSH2_AGENTC_ADD_IDENTITY                17
 #define SSH2_AGENTC_REMOVE_IDENTITY             18
 #define SSH2_AGENTC_REMOVE_ALL_IDENTITIES       19
+#define SSH2_AGENTC_EXTENSION                   27
+#define SSH_AGENT_EXTENSION_FAILURE             28
 
 /*
  * Assorted other SSH-related enumerations.
@@ -1594,8 +1719,7 @@ unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset);
 void add_to_commasep(strbuf *buf, const char *data);
 bool get_commasep_word(ptrlen *list, ptrlen *word);
 
-int verify_ssh_manual_host_key(
-    Conf *conf, const char *fingerprint, ssh_key *key);
+int verify_ssh_manual_host_key(Conf *conf, char **fingerprints, ssh_key *key);
 
 typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache;
 ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void);

+ 7 - 7
source/putty/ssh1bpp.c

@@ -36,13 +36,13 @@ static void ssh1_bpp_queue_disconnect(BinaryPacketProtocol *bpp,
                                       const char *msg, int category);
 static PktOut *ssh1_bpp_new_pktout(int type);
 
-static const struct BinaryPacketProtocolVtable ssh1_bpp_vtable = {
-    ssh1_bpp_free,
-    ssh1_bpp_handle_input,
-    ssh1_bpp_handle_output,
-    ssh1_bpp_new_pktout,
-    ssh1_bpp_queue_disconnect,
-    0xFFFFFFFF, /* no special packet size limit for this bpp */
+static const BinaryPacketProtocolVtable ssh1_bpp_vtable = {
+    .free = ssh1_bpp_free,
+    .handle_input = ssh1_bpp_handle_input,
+    .handle_output = ssh1_bpp_handle_output,
+    .new_pktout = ssh1_bpp_new_pktout,
+    .queue_disconnect = ssh1_bpp_queue_disconnect,
+    .packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */
 };
 
 BinaryPacketProtocol *ssh1_bpp_new(LogContext *logctx)

+ 45 - 31
source/putty/ssh1connection-client.c

@@ -154,7 +154,7 @@ bool ssh1_handle_direction_specific_packet(
         remid = get_uint32(pktin);
 
         /* Refuse if agent forwarding is disabled. */
-        if (!s->agent_fwd_enabled) {
+        if (!ssh_agent_forwarding_permitted(&s->cl)) {
             pktout = ssh_bpp_new_pktout(
                 s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE);
             put_uint32(pktout, remid);
@@ -164,9 +164,31 @@ bool ssh1_handle_direction_specific_packet(
             c->connlayer = s;
             ssh1_channel_init(c);
             c->remoteid = remid;
-            c->chan = agentf_new(&c->sc);
             c->halfopen = false;
 
+            /*
+             * If possible, make a stream-oriented connection to the
+             * agent and set up an ordinary port-forwarding type
+             * channel over it.
+             */
+            Plug *plug;
+            Channel *ch = portfwd_raw_new(&s->cl, &plug, true);
+            Socket *skt = agent_connect(plug);
+            if (!sk_socket_error(skt)) {
+                portfwd_raw_setup(ch, skt, &c->sc);
+                c->chan = ch;
+            } else {
+                portfwd_raw_free(ch);
+
+                /*
+                 * Otherwise, fall back to the old-fashioned system of
+                 * parsing the forwarded data stream ourselves for
+                 * message boundaries, and passing each individual
+                 * message to the one-off agent_query().
+                 */
+                c->chan = agentf_new(&c->sc);
+            }
+
             pktout = ssh_bpp_new_pktout(
                 s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
             put_uint32(pktout, c->remoteid);
@@ -243,15 +265,14 @@ bool ssh1_handle_direction_specific_packet(
 
         return true;
 
-      case SSH1_SMSG_EXIT_STATUS:
-        {
-            int exitcode = get_uint32(pktin);
-            ppl_logevent("Server sent command exit status %d", exitcode);
-            ssh_got_exitcode(s->ppl.ssh, exitcode);
+      case SSH1_SMSG_EXIT_STATUS: {
+        int exitcode = get_uint32(pktin);
+        ppl_logevent("Server sent command exit status %d", exitcode);
+        ssh_got_exitcode(s->ppl.ssh, exitcode);
 
-            s->session_terminated = true;
-        }
+        s->session_terminated = true;
         return true;
+      }
 
       default:
         return false;
@@ -423,28 +444,21 @@ static void ssh1mainchan_write_eof(SshChannel *sc)
     pq_push(s->ppl.out_pq, pktout);
 }
 
-static const struct SshChannelVtable ssh1mainchan_vtable = {
-    ssh1mainchan_write,
-    ssh1mainchan_write_eof,
-    NULL /* unclean_close */,
-    NULL /* unthrottle */,
-    NULL /* get_conf */,
-    NULL /* window_override_removed is only used by SSH-2 sharing */,
-    NULL /* x11_sharing_handover, likewise */,
-    NULL /* send_exit_status */,
-    NULL /* send_exit_signal */,
-    NULL /* send_exit_signal_numeric */,
-    ssh1mainchan_request_x11_forwarding,
-    ssh1mainchan_request_agent_forwarding,
-    ssh1mainchan_request_pty,
-    ssh1mainchan_send_env_var,
-    ssh1mainchan_start_shell,
-    ssh1mainchan_start_command,
-    ssh1mainchan_start_subsystem,
-    ssh1mainchan_send_serial_break,
-    ssh1mainchan_send_signal,
-    ssh1mainchan_send_terminal_size_change,
-    ssh1mainchan_hint_channel_is_simple,
+static const SshChannelVtable ssh1mainchan_vtable = {
+    .write = ssh1mainchan_write,
+    .write_eof = ssh1mainchan_write_eof,
+    .request_x11_forwarding = ssh1mainchan_request_x11_forwarding,
+    .request_agent_forwarding = ssh1mainchan_request_agent_forwarding,
+    .request_pty = ssh1mainchan_request_pty,
+    .send_env_var = ssh1mainchan_send_env_var,
+    .start_shell = ssh1mainchan_start_shell,
+    .start_command = ssh1mainchan_start_command,
+    .start_subsystem = ssh1mainchan_start_subsystem,
+    .send_serial_break = ssh1mainchan_send_serial_break,
+    .send_signal = ssh1mainchan_send_signal,
+    .send_terminal_size_change = ssh1mainchan_send_terminal_size_change,
+    .hint_channel_is_simple = ssh1mainchan_hint_channel_is_simple,
+    /* other methods are NULL */
 };
 
 static void ssh1_session_confirm_callback(void *vctx)

+ 36 - 66
source/putty/ssh1connection.c

@@ -39,16 +39,16 @@ static void ssh1_connection_got_user_input(PacketProtocolLayer *ppl);
 static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
 static unsigned int ssh1_connection_winscp_query(PacketProtocolLayer *ppl, int query);
 
-static const struct PacketProtocolLayerVtable ssh1_connection_vtable = {
-    ssh1_connection_free,
-    ssh1_connection_process_queue,
-    ssh1_common_get_specials,
-    ssh1_connection_special_cmd,
-    ssh1_connection_want_user_input,
-    ssh1_connection_got_user_input,
-    ssh1_connection_reconfigure,
-    ssh_ppl_default_queued_data_size,
-    NULL /* no layer names in SSH-1 */,
+static const PacketProtocolLayerVtable ssh1_connection_vtable = {
+    .free = ssh1_connection_free,
+    .process_queue = ssh1_connection_process_queue,
+    .get_specials = ssh1_common_get_specials,
+    .special_cmd = ssh1_connection_special_cmd,
+    .want_user_input = ssh1_connection_want_user_input,
+    .got_user_input = ssh1_connection_got_user_input,
+    .reconfigure = ssh1_connection_reconfigure,
+    .queued_data_size = ssh_ppl_default_queued_data_size,
+    .name = NULL, /* no layer names in SSH-1 */
     ssh1_connection_winscp_query,
 };
 
@@ -67,34 +67,26 @@ static void ssh1_throttle_all_channels(ConnectionLayer *cl, bool throttled);
 static bool ssh1_ldisc_option(ConnectionLayer *cl, int option);
 static void ssh1_set_ldisc_option(ConnectionLayer *cl, int option, bool value);
 static void ssh1_enable_x_fwd(ConnectionLayer *cl);
-static void ssh1_enable_agent_fwd(ConnectionLayer *cl);
 static void ssh1_set_wants_user_input(ConnectionLayer *cl, bool wanted);
 
-static const struct ConnectionLayerVtable ssh1_connlayer_vtable = {
-    ssh1_rportfwd_alloc,
-    ssh1_rportfwd_remove,
-    ssh1_lportfwd_open,
-    ssh1_session_open,
-    ssh1_serverside_x11_open,
-    ssh1_serverside_agent_open,
-    ssh1_add_x11_display,
-    NULL /* add_sharing_x11_display */,
-    NULL /* remove_sharing_x11_display */,
-    NULL /* send_packet_from_downstream */,
-    NULL /* alloc_sharing_channel */,
-    NULL /* delete_sharing_channel */,
-    NULL /* sharing_queue_global_request */,
-    NULL /* sharing_no_more_downstreams */,
-    ssh1_agent_forwarding_permitted,
-    ssh1_terminal_size,
-    ssh1_stdout_unthrottle,
-    ssh1_stdin_backlog,
-    ssh1_throttle_all_channels,
-    ssh1_ldisc_option,
-    ssh1_set_ldisc_option,
-    ssh1_enable_x_fwd,
-    ssh1_enable_agent_fwd,
-    ssh1_set_wants_user_input,
+static const ConnectionLayerVtable ssh1_connlayer_vtable = {
+    .rportfwd_alloc = ssh1_rportfwd_alloc,
+    .rportfwd_remove = ssh1_rportfwd_remove,
+    .lportfwd_open = ssh1_lportfwd_open,
+    .session_open = ssh1_session_open,
+    .serverside_x11_open = ssh1_serverside_x11_open,
+    .serverside_agent_open = ssh1_serverside_agent_open,
+    .add_x11_display = ssh1_add_x11_display,
+    .agent_forwarding_permitted = ssh1_agent_forwarding_permitted,
+    .terminal_size = ssh1_terminal_size,
+    .stdout_unthrottle = ssh1_stdout_unthrottle,
+    .stdin_backlog = ssh1_stdin_backlog,
+    .throttle_all_channels = ssh1_throttle_all_channels,
+    .ldisc_option = ssh1_ldisc_option,
+    .set_ldisc_option = ssh1_set_ldisc_option,
+    .enable_x_fwd = ssh1_enable_x_fwd,
+    .set_wants_user_input = ssh1_set_wants_user_input,
+    /* other methods are NULL */
 };
 
 static size_t ssh1channel_write(
@@ -105,28 +97,14 @@ static void ssh1channel_unthrottle(SshChannel *c, size_t bufsize);
 static Conf *ssh1channel_get_conf(SshChannel *c);
 static void ssh1channel_window_override_removed(SshChannel *c) { /* ignore */ }
 
-static const struct SshChannelVtable ssh1channel_vtable = {
-    ssh1channel_write,
-    ssh1channel_write_eof,
-    ssh1channel_initiate_close,
-    ssh1channel_unthrottle,
-    ssh1channel_get_conf,
-    ssh1channel_window_override_removed,
-    NULL /* x11_sharing_handover is only used by SSH-2 connection sharing */,
-    NULL /* send_exit_status */,
-    NULL /* send_exit_signal */,
-    NULL /* send_exit_signal_numeric */,
-    NULL /* request_x11_forwarding */,
-    NULL /* request_agent_forwarding */,
-    NULL /* request_pty */,
-    NULL /* send_env_var */,
-    NULL /* start_shell */,
-    NULL /* start_command */,
-    NULL /* start_subsystem */,
-    NULL /* send_serial_break */,
-    NULL /* send_signal */,
-    NULL /* send_terminal_size_change */,
-    NULL /* hint_channel_is_simple */,
+static const SshChannelVtable ssh1channel_vtable = {
+    .write = ssh1channel_write,
+    .write_eof = ssh1channel_write_eof,
+    .initiate_close = ssh1channel_initiate_close,
+    .unthrottle = ssh1channel_unthrottle,
+    .get_conf = ssh1channel_get_conf,
+    .window_override_removed = ssh1channel_window_override_removed,
+    /* everything else is NULL */
 };
 
 static void ssh1_channel_try_eof(struct ssh1_channel *c);
@@ -795,14 +773,6 @@ static void ssh1_enable_x_fwd(ConnectionLayer *cl)
     s->X11_fwd_enabled = true;
 }
 
-static void ssh1_enable_agent_fwd(ConnectionLayer *cl)
-{
-    struct ssh1_connection_state *s =
-        container_of(cl, struct ssh1_connection_state, cl);
-
-    s->agent_fwd_enabled = true;
-}
-
 static void ssh1_set_wants_user_input(ConnectionLayer *cl, bool wanted)
 {
     struct ssh1_connection_state *s =

+ 0 - 4
source/putty/ssh1connection.h

@@ -5,8 +5,6 @@ struct outstanding_succfail;
 struct ssh1_connection_state {
     int crState;
 
-    Ssh *ssh;
-
     Conf *conf;
     int local_protoflags, remote_protoflags;
 
@@ -33,8 +31,6 @@ struct ssh1_connection_state {
     struct X11FakeAuth *x11auth;
     tree234 *x11authtree;
 
-    bool agent_fwd_enabled;
-
     tree234 *rportfwds;
     PortFwdManager *portfwdmgr;
     bool portfwdmgr_configured;

+ 32 - 32
source/putty/ssh1login.c

@@ -79,16 +79,16 @@ static void ssh1_login_got_user_input(PacketProtocolLayer *ppl);
 static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
 static unsigned int ssh1_login_winscp_query(PacketProtocolLayer *ppl, int query);
 
-static const struct PacketProtocolLayerVtable ssh1_login_vtable = {
-    ssh1_login_free,
-    ssh1_login_process_queue,
-    ssh1_common_get_specials,
-    ssh1_login_special_cmd,
-    ssh1_login_want_user_input,
-    ssh1_login_got_user_input,
-    ssh1_login_reconfigure,
-    ssh_ppl_default_queued_data_size,
-    NULL /* no layer names in SSH-1 */,
+static const PacketProtocolLayerVtable ssh1_login_vtable = {
+    .free = ssh1_login_free,
+    .process_queue = ssh1_login_process_queue,
+    .get_specials = ssh1_common_get_specials,
+    .special_cmd = ssh1_login_special_cmd,
+    .want_user_input = ssh1_login_want_user_input,
+    .got_user_input = ssh1_login_got_user_input,
+    .reconfigure = ssh1_login_reconfigure,
+    .queued_data_size = ssh_ppl_default_queued_data_size,
+    .name = NULL, /* no layer names in SSH-1 */
     ssh1_login_winscp_query,
 };
 
@@ -247,23 +247,24 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
         /*
          * First format the key into a string.
          */
-        char *fingerprint;
         char *keystr = rsastr_fmt(&s->hostkey);
-        fingerprint = rsa_ssh1_fingerprint(&s->hostkey);
+        char **fingerprints = rsa_ssh1_fake_all_fingerprints(&s->hostkey);
 
         /* First check against manually configured host keys. */
-        s->dlgret = verify_ssh_manual_host_key(s->conf, fingerprint, NULL);
+        s->dlgret = verify_ssh_manual_host_key(s->conf, fingerprints, NULL);
         if (s->dlgret == 0) {          /* did not match */
-            sfree(fingerprint);
+            ssh2_free_all_fingerprints(fingerprints);
             sfree(keystr);
             ssh_proto_error(s->ppl.ssh, "Host key did not appear in manually "
                             "configured list");
             return;
         } else if (s->dlgret < 0) { /* none configured; use standard handling */
+            char *keydisp = ssh1_pubkey_str(&s->hostkey);
             s->dlgret = seat_verify_ssh_host_key(
-                s->ppl.seat, s->savedhost, s->savedport,
-                "rsa", keystr, fingerprint, ssh1_login_dialog_callback, s);
-            sfree(fingerprint);
+                s->ppl.seat, s->savedhost, s->savedport, "rsa", keystr,
+                keydisp, fingerprints, ssh1_login_dialog_callback, s);
+            sfree(keydisp);
+            ssh2_free_all_fingerprints(fingerprints);
             sfree(keystr);
 #ifdef FUZZING
             s->dlgret = 1;
@@ -276,7 +277,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
                 return;
             }
         } else {
-            sfree(fingerprint);
+            ssh2_free_all_fingerprints(fingerprints);
             sfree(keystr);
         }
     }
@@ -441,7 +442,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
     pq_push(s->ppl.out_pq, pkt);
 
     ppl_logevent(WINSCP_BOM "Sent username \"%s\"", s->username);
-    if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE))
+    if (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat))
         ppl_printf("Sent username \"%s\"\r\n", s->username);
 
     crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
@@ -466,13 +467,13 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
             keytype == SSH_KEYTYPE_SSH1_PUBLIC) {
             const char *error;
             s->publickey_blob = strbuf_new();
-            if (rsa_ssh1_loadpub(s->keyfile,
-                                 BinarySink_UPCAST(s->publickey_blob),
-                                 &s->publickey_comment, &error)) {
+            if (rsa1_loadpub_f(s->keyfile,
+                               BinarySink_UPCAST(s->publickey_blob),
+                               &s->publickey_comment, &error)) {
                 s->privatekey_available = (keytype == SSH_KEYTYPE_SSH1);
                 if (!s->privatekey_available)
                     ppl_logevent("Key file contains public key only");
-                s->privatekey_encrypted = rsa_ssh1_encrypted(s->keyfile, NULL);
+                s->privatekey_encrypted = rsa1_encrypted_f(s->keyfile, NULL);
             } else {
                 ppl_logevent("Unable to load key (%s)", error);
                 ppl_printf(WINSCP_BOM "Unable to load key file \"%s\" (%s)\r\n",
@@ -665,7 +666,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
                             if (pktin->type == SSH1_SMSG_SUCCESS) {
                                 ppl_logevent("Pageant's response "
                                              "accepted");
-                                if (flags & FLAG_VERBOSE) {
+                                if (seat_verbose(s->ppl.seat)) {
                                     ptrlen comment = ptrlen_from_strbuf(
                                         s->agent_keys[s->agent_key_index].
                                         comment);
@@ -700,7 +701,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
              * key file.
              */
             bool got_passphrase; /* need not be kept over crReturn */
-            if (flags & FLAG_VERBOSE)
+            if (seat_verbose(s->ppl.seat))
                 ppl_printf("Trying public key authentication.\r\n");
             ppl_logevent(WINSCP_BOM "Trying public key \"%s\"",
                          filename_to_str(s->keyfile));
@@ -714,7 +715,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
                 char *passphrase = NULL;    /* only written after crReturn */
                 const char *error;
                 if (!s->privatekey_encrypted) {
-                    if (flags & FLAG_VERBOSE)
+                    if (seat_verbose(s->ppl.seat))
                         ppl_printf("No passphrase required.\r\n");
                     passphrase = NULL;
                 } else {
@@ -753,8 +754,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
                 /*
                  * Try decrypting key with passphrase.
                  */
-                retd = rsa_ssh1_loadkey(
-                    s->keyfile, &s->key, passphrase, &error);
+                retd = rsa1_load_f(s->keyfile, &s->key, passphrase, &error);
                 if (passphrase) {
                     smemclr(passphrase, strlen(passphrase));
                     sfree(passphrase);
@@ -772,7 +772,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
                     got_passphrase = false;
                     /* and try again */
                 } else {
-                    unreachable("unexpected return from rsa_ssh1_loadkey()");
+                    unreachable("unexpected return from rsa1_load_f()");
                 }
             }
 
@@ -837,7 +837,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
                 crMaybeWaitUntilV((pktin = ssh1_login_pop(s))
                                   != NULL);
                 if (pktin->type == SSH1_SMSG_FAILURE) {
-                    if (flags & FLAG_VERBOSE)
+                    if (seat_verbose(s->ppl.seat))
                         ppl_printf("Failed to authenticate with"
                                    " our public key.\r\n");
                     continue;          /* go and try something else */
@@ -870,7 +870,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
             crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
             if (pktin->type == SSH1_SMSG_FAILURE) {
                 ppl_logevent("TIS authentication declined");
-                if (flags & FLAG_INTERACTIVE)
+                if (seat_interactive(s->ppl.seat))
                     ppl_printf("TIS authentication refused.\r\n");
                 s->tis_auth_refused = true;
                 continue;
@@ -1131,7 +1131,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
         s->cur_prompt = NULL;
         crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
         if (pktin->type == SSH1_SMSG_FAILURE) {
-            if (flags & FLAG_VERBOSE)
+            if (seat_verbose(s->ppl.seat))
                 ppl_printf("Access denied\r\n");
             ppl_logevent("Authentication refused");
         } else if (pktin->type != SSH1_SMSG_SUCCESS) {

+ 21 - 7
source/putty/ssh2bpp-bare.c

@@ -25,13 +25,15 @@ static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp);
 static void ssh2_bare_bpp_handle_output(BinaryPacketProtocol *bpp);
 static PktOut *ssh2_bare_bpp_new_pktout(int type);
 
-static const struct BinaryPacketProtocolVtable ssh2_bare_bpp_vtable = {
-    ssh2_bare_bpp_free,
-    ssh2_bare_bpp_handle_input,
-    ssh2_bare_bpp_handle_output,
-    ssh2_bare_bpp_new_pktout,
-    ssh2_bpp_queue_disconnect, /* in sshcommon.c */
-    0x4000, /* packet size limit, per protocol spec in sshshare.c comment */
+static const BinaryPacketProtocolVtable ssh2_bare_bpp_vtable = {
+    .free = ssh2_bare_bpp_free,
+    .handle_input = ssh2_bare_bpp_handle_input,
+    .handle_output = ssh2_bare_bpp_handle_output,
+    .new_pktout = ssh2_bare_bpp_new_pktout,
+    .queue_disconnect = ssh2_bpp_queue_disconnect, /* in sshcommon.c */
+
+    /* packet size limit, per protocol spec in sshshare.c comment */
+    .packet_size_limit = 0x4000,
 };
 
 BinaryPacketProtocol *ssh2_bare_bpp_new(LogContext *logctx)
@@ -108,6 +110,18 @@ static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp)
         s->packetlen--;
         BinarySource_INIT(s->pktin, s->data, s->packetlen);
 
+        if (s->pktin->type == SSH2_MSG_EXT_INFO) {
+            /*
+             * Mild layer violation: EXT_INFO is not permitted in the
+             * bare ssh-connection protocol. Faulting it here means
+             * that ssh2_common_filter_queue doesn't receive it in the
+             * first place unless it's legal to have sent it.
+             */
+            ssh_proto_error(s->bpp.ssh, "Remote side sent SSH2_MSG_EXT_INFO "
+                            "in bare connection protocol");
+            return;
+        }
+
         /*
          * Log incoming packet, possibly omitting sensitive fields.
          */

+ 64 - 7
source/putty/ssh2bpp.c

@@ -37,6 +37,9 @@ struct ssh2_bpp_state {
     bool is_server;
     bool pending_newkeys;
     bool pending_compression, seen_userauth_success;
+    bool enforce_next_packet_is_userauth_success;
+    unsigned nnewkeys;
+    int prev_type;
 
     BinaryPacketProtocol bpp;
 };
@@ -46,13 +49,13 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp);
 static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp);
 static PktOut *ssh2_bpp_new_pktout(int type);
 
-static const struct BinaryPacketProtocolVtable ssh2_bpp_vtable = {
-    ssh2_bpp_free,
-    ssh2_bpp_handle_input,
-    ssh2_bpp_handle_output,
-    ssh2_bpp_new_pktout,
-    ssh2_bpp_queue_disconnect, /* in sshcommon.c */
-    0xFFFFFFFF, /* no special packet size limit for this bpp */
+static const BinaryPacketProtocolVtable ssh2_bpp_vtable = {
+    .free = ssh2_bpp_free,
+    .handle_input = ssh2_bpp_handle_input,
+    .handle_output = ssh2_bpp_handle_output,
+    .new_pktout = ssh2_bpp_new_pktout,
+    .queue_disconnect = ssh2_bpp_queue_disconnect, /* in sshcommon.c */
+    .packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */
 };
 
 BinaryPacketProtocol *ssh2_bpp_new(
@@ -594,9 +597,25 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
 
         {
             int type = s->pktin->type;
+            int prev_type = s->prev_type;
+            s->prev_type = type;
             s->pktin = NULL;
 
+            if (s->enforce_next_packet_is_userauth_success) {
+                /* See EXT_INFO handler below */
+                if (type != SSH2_MSG_USERAUTH_SUCCESS) {
+                    ssh_proto_error(s->bpp.ssh,
+                                    "Remote side sent SSH2_MSG_EXT_INFO "
+                                    "not either preceded by NEWKEYS or "
+                                    "followed by USERAUTH_SUCCESS");
+                    return;
+                }
+                s->enforce_next_packet_is_userauth_success = false;
+            }
+
             if (type == SSH2_MSG_NEWKEYS) {
+                if (s->nnewkeys < 2)
+                    s->nnewkeys++;
                 /*
                  * Mild layer violation: in this situation we must
                  * suspend processing of the input byte stream until
@@ -627,6 +646,44 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
                 s->seen_userauth_success = true;
             }
 
+            if (type == SSH2_MSG_EXT_INFO) {
+                /*
+                 * And another: enforce that an incoming EXT_INFO is
+                 * either the message immediately after the initial
+                 * NEWKEYS, or (if we're the client) the one
+                 * immediately before USERAUTH_SUCCESS.
+                 */
+                if (prev_type == SSH2_MSG_NEWKEYS && s->nnewkeys == 1) {
+                    /* OK - this is right after the first NEWKEYS. */
+                } else if (s->is_server) {
+                    /* We're the server, so they're the client.
+                     * Clients may not send EXT_INFO at _any_ other
+                     * time. */
+                    ssh_proto_error(s->bpp.ssh,
+                                    "Remote side sent SSH2_MSG_EXT_INFO "
+                                    "that was not immediately after the "
+                                    "initial NEWKEYS");
+                    return;
+                } else if (s->nnewkeys > 0 && s->seen_userauth_success) {
+                    /* We're the client, so they're the server. In
+                     * that case they may also send EXT_INFO
+                     * immediately before USERAUTH_SUCCESS. Error out
+                     * immediately if this can't _possibly_ be that
+                     * moment (because we haven't even seen NEWKEYS
+                     * yet, or because we've already seen
+                     * USERAUTH_SUCCESS). */
+                    ssh_proto_error(s->bpp.ssh,
+                                    "Remote side sent SSH2_MSG_EXT_INFO "
+                                    "after USERAUTH_SUCCESS");
+                    return;
+                } else {
+                    /* This _could_ be OK, provided the next packet is
+                     * USERAUTH_SUCCESS. Set a flag to remember to
+                     * fault it if not. */
+                    s->enforce_next_packet_is_userauth_success = true;
+                }
+            }
+
             if (s->pending_compression && userauth_range(type)) {
                 /*
                  * Receiving any userauth message at all indicates

+ 23 - 3
source/putty/ssh2connection-client.c

@@ -89,13 +89,32 @@ static ChanopenResult chan_open_forwarded_tcpip(
 static ChanopenResult chan_open_auth_agent(
     struct ssh2_connection_state *s, SshChannel *sc)
 {
-    if (!s->agent_fwd_enabled) {
+    if (!ssh_agent_forwarding_permitted(&s->cl)) {
         CHANOPEN_RETURN_FAILURE(
             SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
             ("Agent forwarding is not enabled"));
     }
 
-    CHANOPEN_RETURN_SUCCESS(agentf_new(sc));
+    /*
+     * If possible, make a stream-oriented connection to the agent and
+     * set up an ordinary port-forwarding type channel over it.
+     */
+    Plug *plug;
+    Channel *ch = portfwd_raw_new(&s->cl, &plug, true);
+    Socket *skt = agent_connect(plug);
+
+    if (!sk_socket_error(skt)) {
+        portfwd_raw_setup(ch, skt, sc);
+        CHANOPEN_RETURN_SUCCESS(ch);
+    } else {
+        portfwd_raw_free(ch);
+        /*
+         * Otherwise, fall back to the old-fashioned system of parsing the
+         * forwarded data stream ourselves for message boundaries, and
+         * passing each individual message to the one-off agent_query().
+         */
+        CHANOPEN_RETURN_SUCCESS(agentf_new(sc));
+    }
 }
 
 ChanopenResult ssh2_connection_parse_channel_open(
@@ -485,5 +504,6 @@ void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h)
 
 bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s)
 {
-    return !seat_set_trust_status(s->ppl.seat, false);
+    bool success = seat_set_trust_status(s->ppl.seat, false);
+    return (!success && !ssh_is_bare(s->ppl.ssh));
 }

+ 71 - 72
source/putty/ssh2connection.c

@@ -25,16 +25,16 @@ static void ssh2_connection_got_user_input(PacketProtocolLayer *ppl);
 static void ssh2_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
 static unsigned int ssh2_connection_winscp_query(PacketProtocolLayer *ppl, int query);
 
-static const struct PacketProtocolLayerVtable ssh2_connection_vtable = {
-    ssh2_connection_free,
-    ssh2_connection_process_queue,
-    ssh2_connection_get_specials,
-    ssh2_connection_special_cmd,
-    ssh2_connection_want_user_input,
-    ssh2_connection_got_user_input,
-    ssh2_connection_reconfigure,
-    ssh_ppl_default_queued_data_size,
-    "ssh-connection",
+static const PacketProtocolLayerVtable ssh2_connection_vtable = {
+    .free = ssh2_connection_free,
+    .process_queue = ssh2_connection_process_queue,
+    .get_specials = ssh2_connection_get_specials,
+    .special_cmd = ssh2_connection_special_cmd,
+    .want_user_input = ssh2_connection_want_user_input,
+    .got_user_input = ssh2_connection_got_user_input,
+    .reconfigure = ssh2_connection_reconfigure,
+    .queued_data_size = ssh_ppl_default_queued_data_size,
+    .name = "ssh-connection",
     ssh2_connection_winscp_query,
 };
 
@@ -66,34 +66,32 @@ static void ssh2_throttle_all_channels(ConnectionLayer *cl, bool throttled);
 static bool ssh2_ldisc_option(ConnectionLayer *cl, int option);
 static void ssh2_set_ldisc_option(ConnectionLayer *cl, int option, bool value);
 static void ssh2_enable_x_fwd(ConnectionLayer *cl);
-static void ssh2_enable_agent_fwd(ConnectionLayer *cl);
 static void ssh2_set_wants_user_input(ConnectionLayer *cl, bool wanted);
 
-static const struct ConnectionLayerVtable ssh2_connlayer_vtable = {
-    ssh2_rportfwd_alloc,
-    ssh2_rportfwd_remove,
-    ssh2_lportfwd_open,
-    ssh2_session_open,
-    ssh2_serverside_x11_open,
-    ssh2_serverside_agent_open,
-    ssh2_add_x11_display,
-    ssh2_add_sharing_x11_display,
-    ssh2_remove_sharing_x11_display,
-    ssh2_send_packet_from_downstream,
-    ssh2_alloc_sharing_channel,
-    ssh2_delete_sharing_channel,
-    ssh2_sharing_queue_global_request,
-    ssh2_sharing_no_more_downstreams,
-    ssh2_agent_forwarding_permitted,
-    ssh2_terminal_size,
-    ssh2_stdout_unthrottle,
-    ssh2_stdin_backlog,
-    ssh2_throttle_all_channels,
-    ssh2_ldisc_option,
-    ssh2_set_ldisc_option,
-    ssh2_enable_x_fwd,
-    ssh2_enable_agent_fwd,
-    ssh2_set_wants_user_input,
+static const ConnectionLayerVtable ssh2_connlayer_vtable = {
+    .rportfwd_alloc = ssh2_rportfwd_alloc,
+    .rportfwd_remove = ssh2_rportfwd_remove,
+    .lportfwd_open = ssh2_lportfwd_open,
+    .session_open = ssh2_session_open,
+    .serverside_x11_open = ssh2_serverside_x11_open,
+    .serverside_agent_open = ssh2_serverside_agent_open,
+    .add_x11_display = ssh2_add_x11_display,
+    .add_sharing_x11_display = ssh2_add_sharing_x11_display,
+    .remove_sharing_x11_display = ssh2_remove_sharing_x11_display,
+    .send_packet_from_downstream = ssh2_send_packet_from_downstream,
+    .alloc_sharing_channel = ssh2_alloc_sharing_channel,
+    .delete_sharing_channel = ssh2_delete_sharing_channel,
+    .sharing_queue_global_request = ssh2_sharing_queue_global_request,
+    .sharing_no_more_downstreams = ssh2_sharing_no_more_downstreams,
+    .agent_forwarding_permitted = ssh2_agent_forwarding_permitted,
+    .terminal_size = ssh2_terminal_size,
+    .stdout_unthrottle = ssh2_stdout_unthrottle,
+    .stdin_backlog = ssh2_stdin_backlog,
+    .throttle_all_channels = ssh2_throttle_all_channels,
+    .ldisc_option = ssh2_ldisc_option,
+    .set_ldisc_option = ssh2_set_ldisc_option,
+    .enable_x_fwd = ssh2_enable_x_fwd,
+    .set_wants_user_input = ssh2_set_wants_user_input,
 };
 
 static char *ssh2_channel_open_failure_error_text(PktIn *pktin)
@@ -136,28 +134,28 @@ static void ssh2channel_x11_sharing_handover(
     int protomajor, int protominor, const void *initial_data, int initial_len);
 static void ssh2channel_hint_channel_is_simple(SshChannel *c);
 
-static const struct SshChannelVtable ssh2channel_vtable = {
-    ssh2channel_write,
-    ssh2channel_write_eof,
-    ssh2channel_initiate_close,
-    ssh2channel_unthrottle,
-    ssh2channel_get_conf,
-    ssh2channel_window_override_removed,
-    ssh2channel_x11_sharing_handover,
-    ssh2channel_send_exit_status,
-    ssh2channel_send_exit_signal,
-    ssh2channel_send_exit_signal_numeric,
-    ssh2channel_request_x11_forwarding,
-    ssh2channel_request_agent_forwarding,
-    ssh2channel_request_pty,
-    ssh2channel_send_env_var,
-    ssh2channel_start_shell,
-    ssh2channel_start_command,
-    ssh2channel_start_subsystem,
-    ssh2channel_send_serial_break,
-    ssh2channel_send_signal,
-    ssh2channel_send_terminal_size_change,
-    ssh2channel_hint_channel_is_simple,
+static const SshChannelVtable ssh2channel_vtable = {
+    .write = ssh2channel_write,
+    .write_eof = ssh2channel_write_eof,
+    .initiate_close = ssh2channel_initiate_close,
+    .unthrottle = ssh2channel_unthrottle,
+    .get_conf = ssh2channel_get_conf,
+    .window_override_removed = ssh2channel_window_override_removed,
+    .x11_sharing_handover = ssh2channel_x11_sharing_handover,
+    .send_exit_status = ssh2channel_send_exit_status,
+    .send_exit_signal = ssh2channel_send_exit_signal,
+    .send_exit_signal_numeric = ssh2channel_send_exit_signal_numeric,
+    .request_x11_forwarding = ssh2channel_request_x11_forwarding,
+    .request_agent_forwarding = ssh2channel_request_agent_forwarding,
+    .request_pty = ssh2channel_request_pty,
+    .send_env_var = ssh2channel_send_env_var,
+    .start_shell = ssh2channel_start_shell,
+    .start_command = ssh2channel_start_command,
+    .start_subsystem = ssh2channel_start_subsystem,
+    .send_serial_break = ssh2channel_send_serial_break,
+    .send_signal = ssh2channel_send_signal,
+    .send_terminal_size_change = ssh2channel_send_terminal_size_change,
+    .hint_channel_is_simple = ssh2channel_hint_channel_is_simple,
 };
 
 static void ssh2_channel_check_close(struct ssh2_channel *c);
@@ -524,19 +522,18 @@ static bool ssh2_connection_filter_queue(struct ssh2_connection_state *s)
                     ssh2_channel_try_eof(c); /* in case we had a pending EOF */
                 break;
 
-              case SSH2_MSG_CHANNEL_OPEN_FAILURE:
+              case SSH2_MSG_CHANNEL_OPEN_FAILURE: {
                 assert(c->halfopen);
 
-                {
-                    char *err = ssh2_channel_open_failure_error_text(pktin);
-                    chan_open_failed(c->chan, err);
-                    sfree(err);
-                }
+                char *err = ssh2_channel_open_failure_error_text(pktin);
+                chan_open_failed(c->chan, err);
+                sfree(err);
 
                 del234(s->channels, c);
                 ssh2_channel_free(c);
 
                 break;
+              }
 
               case SSH2_MSG_CHANNEL_DATA:
               case SSH2_MSG_CHANNEL_EXTENDED_DATA:
@@ -1031,6 +1028,7 @@ static void ssh2_connection_process_queue(PacketProtocolLayer *ppl)
     s->mainchan = mainchan_new(
         &s->ppl, &s->cl, s->conf, s->term_width, s->term_height,
         s->ssh_is_simple, &s->mainchan_sc);
+    s->started = true;
     // WINSCP
     if (!s->mainchan)
     {
@@ -1264,6 +1262,15 @@ static void ssh2_check_termination(struct ssh2_connection_state *s)
     if (s->persistent)
         return;     /* persistent mode: never proactively terminate */
 
+    if (!s->started) {
+        /* At startup, we don't have any channels open because we
+         * haven't got round to opening the main one yet. In that
+         * situation, we don't want to terminate, even if a sharing
+         * connection opens and closes and causes a call to this
+         * function. */
+        return;
+    }
+
     if (count234(s->channels) == 0 &&
         !(s->connshare && share_ndownstreams(s->connshare) > 0)) {
         /*
@@ -1708,14 +1715,6 @@ static void ssh2_enable_x_fwd(ConnectionLayer *cl)
     s->X11_fwd_enabled = true;
 }
 
-static void ssh2_enable_agent_fwd(ConnectionLayer *cl)
-{
-    struct ssh2_connection_state *s =
-        container_of(cl, struct ssh2_connection_state, cl);
-
-    s->agent_fwd_enabled = true;
-}
-
 static void ssh2_set_wants_user_input(ConnectionLayer *cl, bool wanted)
 {
     struct ssh2_connection_state *s =

+ 1 - 3
source/putty/ssh2connection.h

@@ -7,8 +7,6 @@ struct outstanding_global_request;
 struct ssh2_connection_state {
     int crState;
 
-    Ssh *ssh;
-
     ssh_sharing_state *connshare;
     char *peer_verstring;
 
@@ -22,6 +20,7 @@ struct ssh2_connection_state {
 
     bool ssh_is_simple;
     bool persistent;
+    bool started;
 
     Conf *conf;
 
@@ -32,7 +31,6 @@ struct ssh2_connection_state {
     tree234 *x11authtree;
 
     bool got_pty;
-    bool agent_fwd_enabled;
 
     tree234 *rportfwds;
     PortFwdManager *portfwdmgr;

+ 33 - 21
source/putty/ssh2kex-client.c

@@ -13,6 +13,12 @@
 #include "ssh2transport.h"
 #include "mpint.h"
 
+/*
+ * Another copy of the symbol defined in mpunsafe.c. See the comment
+ * there.
+ */
+const int deliberate_symbol_clash = 12345;
+
 void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
 {
     PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
@@ -250,7 +256,6 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
         s->init_token_sent = false;
         s->complete_rcvd = false;
         s->hkey = NULL;
-        s->fingerprint = NULL;
         s->keystr = NULL;
 
         /*
@@ -719,11 +724,12 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
              * host key, store it.
              */
             if (s->hkey) {
-                s->fingerprint = ssh2_fingerprint(s->hkey);
+                char *fingerprint = ssh2_fingerprint(
+                    s->hkey, SSH_FPTYPE_DEFAULT);
                 ppl_logevent("GSS kex provided fallback host key:");
-                ppl_logevent("%s", s->fingerprint);
-                sfree(s->fingerprint);
-                s->fingerprint = NULL;
+                ppl_logevent("%s", fingerprint);
+                sfree(fingerprint);
+
                 ssh_transient_hostkey_cache_add(s->thc, s->hkey);
             } else if (!ssh_transient_hostkey_cache_non_empty(s->thc)) {
                 /*
@@ -777,25 +783,25 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
              * triggered on purpose to populate the transient cache.
              */
             assert(s->hkey);  /* only KEXTYPE_GSS lets this be null */
-            s->fingerprint = ssh2_fingerprint(s->hkey);
+            char *fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT);
 
             if (s->need_gss_transient_hostkey) {
                 ppl_logevent("Post-GSS rekey provided fallback host key:");
-                ppl_logevent("%s", s->fingerprint);
+                ppl_logevent("%s", fingerprint);
                 ssh_transient_hostkey_cache_add(s->thc, s->hkey);
                 s->need_gss_transient_hostkey = false;
             } else if (!ssh_transient_hostkey_cache_verify(s->thc, s->hkey)) {
                 ppl_logevent("Non-GSS rekey after initial GSS kex "
                              "used host key:");
-                ppl_logevent("%s", s->fingerprint);
+                ppl_logevent("%s", fingerprint);
+                sfree(fingerprint);
                 ssh_sw_abort(s->ppl.ssh, "Server's host key did not match any "
                              "used in previous GSS kex");
                 *aborted = true;
                 return;
             }
 
-            sfree(s->fingerprint);
-            s->fingerprint = NULL;
+            sfree(fingerprint);
         }
     } else
 #endif /* NO_GSSAPI */
@@ -841,22 +847,29 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
              * Authenticate remote host: verify host key. (We've already
              * checked the signature of the exchange hash.)
              */
-            s->fingerprint = ssh2_fingerprint(s->hkey);
+            char **fingerprints = ssh2_all_fingerprints(s->hkey);
+            FingerprintType fptype_default =
+                ssh2_pick_default_fingerprint(fingerprints);
             ppl_logevent("Host key fingerprint is:");
-            ppl_logevent("%s", s->fingerprint);
+            ppl_logevent("%s", fingerprints[fptype_default]);
             /* First check against manually configured host keys. */
             s->dlgret = verify_ssh_manual_host_key(
-                s->conf, s->fingerprint, s->hkey);
+                s->conf, fingerprints, s->hkey);
             if (s->dlgret == 0) {          /* did not match */
+                ssh2_free_all_fingerprints(fingerprints);
                 ssh_sw_abort(s->ppl.ssh, "Host key did not appear in manually "
                              "configured list");
                 *aborted = true;
                 return;
             } else if (s->dlgret < 0) { /* none configured; use standard handling */
+                ssh2_userkey uk = { .key = s->hkey, .comment = NULL };
+                char *keydisp = ssh2_pubkey_openssh_str(&uk);
                 s->dlgret = seat_verify_ssh_host_key(
                     s->ppl.seat, s->savedhost, s->savedport,
-                    ssh_key_cache_id(s->hkey), s->keystr, s->fingerprint,
-                    ssh2_transport_dialog_callback, s);
+                    ssh_key_cache_id(s->hkey), s->keystr, keydisp,
+                    fingerprints, ssh2_transport_dialog_callback, s);
+                sfree(keydisp);
+                ssh2_free_all_fingerprints(fingerprints);
 #ifdef FUZZING
                 s->dlgret = 1;
 #endif
@@ -868,8 +881,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
                     return;
                 }
             }
-            sfree(s->fingerprint);
-            s->fingerprint = NULL;
+
             /*
              * Save this host key, to check against the one presented in
              * subsequent rekeys.
@@ -880,11 +892,11 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
             assert(s->hkey);
             assert(ssh_key_alg(s->hkey) == s->cross_certifying);
 
-            s->fingerprint = ssh2_fingerprint(s->hkey);
+            char *fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT);
             ppl_logevent("Storing additional host key for this host:");
-            ppl_logevent("%s", s->fingerprint);
-            sfree(s->fingerprint);
-            s->fingerprint = NULL;
+            ppl_logevent("%s", fingerprint);
+            sfree(fingerprint);
+
             store_host_key(s->savedhost, s->savedport,
                            ssh_key_cache_id(s->hkey), s->keystr);
             /*

+ 163 - 27
source/putty/ssh2transport.c

@@ -54,11 +54,16 @@ static bool ssh_decomp_none_block(ssh_decompressor *handle,
 {
     return false;
 }
-const static ssh_compression_alg ssh_comp_none = {
-    "none", NULL,
-    ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block,
-    ssh_decomp_none_init, ssh_decomp_none_cleanup, ssh_decomp_none_block,
-    NULL
+static const ssh_compression_alg ssh_comp_none = {
+    .name = "none",
+    .delayed_name = NULL,
+    .compress_new = ssh_comp_none_init,
+    .compress_free = ssh_comp_none_cleanup,
+    .compress = ssh_comp_none_block,
+    .decompress_new = ssh_decomp_none_init,
+    .decompress_free = ssh_decomp_none_cleanup,
+    .decompress = ssh_decomp_none_block,
+    .text_name = NULL,
 };
 const static ssh_compression_alg *const compressions[] = {
     &ssh_zlib, &ssh_comp_none
@@ -80,16 +85,16 @@ static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def);
 static void ssh2_transport_higher_layer_packet_callback(void *context);
 static unsigned int ssh2_transport_winscp_query(PacketProtocolLayer *ppl, int query);
 
-static const struct PacketProtocolLayerVtable ssh2_transport_vtable = {
-    ssh2_transport_free,
-    ssh2_transport_process_queue,
-    ssh2_transport_get_specials,
-    ssh2_transport_special_cmd,
-    ssh2_transport_want_user_input,
-    ssh2_transport_got_user_input,
-    ssh2_transport_reconfigure,
-    ssh2_transport_queued_data_size,
-    NULL, /* no protocol name for this layer */
+static const PacketProtocolLayerVtable ssh2_transport_vtable = {
+    .free = ssh2_transport_free,
+    .process_queue = ssh2_transport_process_queue,
+    .get_specials = ssh2_transport_get_specials,
+    .special_cmd = ssh2_transport_special_cmd,
+    .want_user_input = ssh2_transport_want_user_input,
+    .got_user_input = ssh2_transport_got_user_input,
+    .reconfigure = ssh2_transport_reconfigure,
+    .queued_data_size = ssh2_transport_queued_data_size,
+    .name = NULL, /* no protocol name for this layer */
     ssh2_transport_winscp_query,
 };
 
@@ -216,7 +221,6 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl)
     sfree(s->keystr);
     sfree(s->hostkey_str);
     strbuf_free(s->hostkeyblob);
-    sfree(s->fingerprint);
     if (s->hkey && !s->hostkeys) {
         ssh_key_free(s->hkey);
         s->hkey = NULL;
@@ -282,27 +286,25 @@ static void ssh2_mkkey(
     put_data(h, H, hlen);
     put_byte(h, chr);
     put_data(h, s->session_id, s->session_id_len);
-    ssh_hash_final(h, key);
+    ssh_hash_digest(h, key);
 
     /* Subsequent blocks of hlen bytes. */
     if (keylen_padded > hlen) {
         int offset;
 
-        h = ssh_hash_new(s->kex_alg->hash);
+        ssh_hash_reset(h);
         if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY))
             put_mp_ssh2(h, K);
         put_data(h, H, hlen);
 
         for (offset = hlen; offset < keylen_padded; offset += hlen) {
             put_data(h, key + offset - hlen, hlen);
-            { // WINSCP
-            ssh_hash *h2 = ssh_hash_copy(h);
-            ssh_hash_final(h2, key + offset);
-            } // WINSCP
+            ssh_hash_digest_nondestructive(h, key + offset);
         }
 
-        ssh_hash_free(h);
     }
+
+    ssh_hash_free(h);
 }
 
 /*
@@ -380,6 +382,64 @@ bool ssh2_common_filter_queue(PacketProtocolLayer *ppl)
             pq_pop(ppl->in_pq);
             break;
 
+          case SSH2_MSG_EXT_INFO: {
+            /*
+             * The BPP enforces that these turn up only at legal
+             * points in the protocol. In particular, it will not pass
+             * an EXT_INFO on to us if it arrives before encryption is
+             * enabled (which is when a MITM could inject one
+             * maliciously).
+             *
+             * However, one of the criteria for legality is that a
+             * server is permitted to send this message immediately
+             * _before_ USERAUTH_SUCCESS. So we may receive this
+             * message not yet knowing whether it's legal to have sent
+             * it - we won't know until the BPP processes the next
+             * packet.
+             *
+             * But that should be OK, because firstly, an
+             * out-of-sequence EXT_INFO that's still within the
+             * encrypted session is only a _protocol_ violation, not
+             * an attack; secondly, any data we set in response to
+             * such an illegal EXT_INFO won't have a chance to affect
+             * the session before the BPP aborts it anyway.
+             */
+            uint32_t nexts = get_uint32(pktin);
+            for (uint32_t i = 0; i < nexts && !get_err(pktin); i++) {
+                ptrlen extname = get_string(pktin);
+                ptrlen extvalue = get_string(pktin);
+                if (ptrlen_eq_string(extname, "server-sig-algs")) {
+                    /*
+                     * Server has sent a list of signature algorithms
+                     * it will potentially accept for user
+                     * authentication keys. Check in particular
+                     * whether the RFC 8332 improved versions of
+                     * ssh-rsa are in the list, and set flags in the
+                     * BPP if so.
+                     *
+                     * TODO: another thing we _could_ do here is to
+                     * record a full list of the algorithm identifiers
+                     * we've seen, whether we understand them
+                     * ourselves or not. Then we could use that as a
+                     * pre-filter during userauth, to skip keys in the
+                     * SSH agent if we already know the server can't
+                     * possibly accept them. (Even if the key
+                     * algorithm is one that the agent and the server
+                     * both understand but we do not.)
+                     */
+                    ptrlen algname;
+                    while (get_commasep_word(&extvalue, &algname)) {
+                        if (ptrlen_eq_string(algname, "rsa-sha2-256"))
+                            ppl->bpp->ext_info_rsa_sha256_ok = true;
+                        if (ptrlen_eq_string(algname, "rsa-sha2-512"))
+                            ppl->bpp->ext_info_rsa_sha512_ok = true;
+                    }
+                }
+            }
+            pq_pop(ppl->in_pq);
+            break;
+          }
+
           default:
             return false;
         }
@@ -571,10 +631,27 @@ static void ssh2_write_kexinit_lists(
          * host keys we actually have.
          */
         for (i = 0; i < our_nhostkeys; i++) {
+            const ssh_keyalg *keyalg = ssh_key_alg(our_hostkeys[i]);
+
             alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY],
-                                      ssh_key_alg(our_hostkeys[i])->ssh_id);
-            alg->u.hk.hostkey = ssh_key_alg(our_hostkeys[i]);
+                                      keyalg->ssh_id);
+            alg->u.hk.hostkey = keyalg;
+            alg->u.hk.hkflags = 0;
             alg->u.hk.warn = false;
+
+            if (keyalg == &ssh_rsa) {
+                alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY],
+                                          "rsa-sha2-256");
+                alg->u.hk.hostkey = keyalg;
+                alg->u.hk.hkflags = SSH_AGENT_RSA_SHA2_256;
+                alg->u.hk.warn = false;
+
+                alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY],
+                                          "rsa-sha2-512");
+                alg->u.hk.hostkey = keyalg;
+                alg->u.hk.hkflags = SSH_AGENT_RSA_SHA2_512;
+                alg->u.hk.warn = false;
+            }
         }
     } else if (first_time) {
         /*
@@ -768,6 +845,12 @@ static void ssh2_write_kexinit_lists(
         #ifndef WINSCP
         }
         #endif
+        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");
+        }
         put_stringsb(pktout, list);
     }
     /* List client->server languages. Empty list. */
@@ -783,7 +866,8 @@ static bool ssh2_scan_kexinits(
     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,
-    int *n_server_hostkeys, int server_hostkeys[MAXKEXLIST])
+    int *n_server_hostkeys, int server_hostkeys[MAXKEXLIST], unsigned *hkflags,
+    bool *can_send_ext_info)
 {
     BinarySource client[1], server[1];
     int i;
@@ -944,6 +1028,7 @@ static bool ssh2_scan_kexinits(
                 continue;
 
             *hostkey_alg = alg->u.hk.hostkey;
+            *hkflags = alg->u.hk.hkflags;
             *warn_hk = alg->u.hk.warn;
             break;
 
@@ -982,6 +1067,20 @@ 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 (server_hostkeys) {
         /*
          * Finally, make an auxiliary pass over the server's host key
@@ -1155,7 +1254,8 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
                 ptrlen_from_strbuf(s->server_kexinit),
                 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, &nhk, hks))
+                &s->warn_sccipher, s->ppl.ssh, NULL, &s->ignorepkt, &nhk, hks,
+                &s->hkflags, &s->can_send_ext_info))
             return; /* false means a fatal error function was called */
 
         /*
@@ -1354,6 +1454,42 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
         strbuf_free(mac_key);
     }
 
+    /*
+     * If that was our first key exchange, this is the moment to send
+     * our EXT_INFO, if we're sending one.
+     */
+    if (!s->post_newkeys_ext_info) {
+        s->post_newkeys_ext_info = true; /* never do this again */
+        if (s->can_send_ext_info) {
+            strbuf *extinfo = strbuf_new();
+            uint32_t n_exts = 0;
+
+            if (s->ssc) {
+                /* Server->client EXT_INFO lists our supported user
+                 * key algorithms. */
+                n_exts++;
+                put_stringz(extinfo, "server-sig-algs");
+                strbuf *list = strbuf_new();
+                for (size_t i = 0; i < n_keyalgs; i++)
+                    add_to_commasep(list, all_keyalgs[i]->ssh_id);
+                put_stringsb(extinfo, list);
+            } else {
+                /* Client->server EXT_INFO is currently not sent, but here's
+                 * where we should put things in it if we ever want to. */
+            }
+
+            /* Only send EXT_INFO if it's non-empty */
+            if (n_exts) {
+                pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_EXT_INFO);
+                put_uint32(pktout, n_exts);
+                put_datapl(pktout, ptrlen_from_strbuf(extinfo));
+                pq_push(s->ppl.out_pq, pktout);
+            }
+
+            strbuf_free(extinfo);
+        }
+    }
+
     /*
      * Now our end of the key exchange is complete, we can send all
      * our queued higher-layer packets. Transfer the whole of the next

+ 7 - 1
source/putty/ssh2transport.h

@@ -28,6 +28,7 @@ struct kexinit_algorithm {
         } kex;
         struct {
             const ssh_keyalg *hostkey;
+            unsigned hkflags;
             bool warn;
         } hk;
         struct {
@@ -47,10 +48,13 @@ struct kexinit_algorithm {
 
 #define HOSTKEY_ALGORITHMS(X)                   \
     X(HK_ED25519, ssh_ecdsa_ed25519)            \
+    X(HK_ED448, ssh_ecdsa_ed448)                \
     X(HK_ECDSA, ssh_ecdsa_nistp256)             \
     X(HK_ECDSA, ssh_ecdsa_nistp384)             \
     X(HK_ECDSA, ssh_ecdsa_nistp521)             \
     /* Changed order to match WinSCP default preference list for SshHostKeyList() */ \
+    X(HK_RSA, ssh_rsa_sha512)                   \
+    X(HK_RSA, ssh_rsa_sha256)                   \
     X(HK_RSA, ssh_rsa)                          \
     X(HK_DSA, ssh_dss)                          \
     /* end of list */
@@ -171,8 +175,9 @@ struct ssh2_transport_state {
     transport_direction in, out, *cstrans, *sctrans;
     ptrlen hostkeydata, sigdata;
     strbuf *hostkeyblob;
-    char *keystr, *fingerprint;
+    char *keystr;
     ssh_key *hkey;                     /* actual host key */
+    unsigned hkflags;                  /* signing flags, used in server */
     RSAKey *rsa_kex_key;             /* for RSA kex */
     bool rsa_kex_key_needs_freeing;
     ecdh_key *ecdh_key;                     /* for ECDH kex */
@@ -181,6 +186,7 @@ struct ssh2_transport_state {
     bool need_gss_transient_hostkey;
     bool warned_about_no_gss_transient_hostkey;
     bool got_session_id;
+    bool can_send_ext_info, post_newkeys_ext_info;
     int dlgret;
     bool guessok;
     bool ignorepkt;

+ 60 - 32
source/putty/ssh2userauth.c

@@ -79,6 +79,8 @@ struct ssh2_userauth_state {
     size_t agent_keys_len;
     agent_key *agent_keys;
     size_t agent_key_index, agent_key_limit;
+    ptrlen agent_keyalg;
+    unsigned signflags;
     int len;
     PktOut *pktout;
     bool want_user_input;
@@ -119,16 +121,16 @@ static PktOut *ssh2_userauth_gss_packet(
 static void ssh2_userauth_antispoof_msg(
     struct ssh2_userauth_state *s, const char *msg);
 
-static const struct PacketProtocolLayerVtable ssh2_userauth_vtable = {
-    ssh2_userauth_free,
-    ssh2_userauth_process_queue,
-    ssh2_userauth_get_specials,
-    ssh2_userauth_special_cmd,
-    ssh2_userauth_want_user_input,
-    ssh2_userauth_got_user_input,
-    ssh2_userauth_reconfigure,
-    ssh_ppl_default_queued_data_size,
-    "ssh-userauth",
+static const PacketProtocolLayerVtable ssh2_userauth_vtable = {
+    .free = ssh2_userauth_free,
+    .process_queue = ssh2_userauth_process_queue,
+    .get_specials = ssh2_userauth_get_specials,
+    .special_cmd = ssh2_userauth_special_cmd,
+    .want_user_input = ssh2_userauth_want_user_input,
+    .got_user_input = ssh2_userauth_got_user_input,
+    .reconfigure = ssh2_userauth_reconfigure,
+    .queued_data_size = ssh_ppl_default_queued_data_size,
+    .name = "ssh-userauth",
 };
 
 PacketProtocolLayer *ssh2_userauth_new(
@@ -289,15 +291,13 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
             keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
             const char *error;
             s->publickey_blob = strbuf_new();
-            if (ssh2_userkey_loadpub(s->keyfile,
-                                     &s->publickey_algorithm,
-                                     BinarySink_UPCAST(s->publickey_blob),
-                                     &s->publickey_comment, &error)) {
+            if (ppk_loadpub_f(s->keyfile, &s->publickey_algorithm,
+                              BinarySink_UPCAST(s->publickey_blob),
+                              &s->publickey_comment, &error)) {
                 s->privatekey_available = (keytype == SSH_KEYTYPE_SSH2);
                 if (!s->privatekey_available)
                     ppl_logevent("Key file contains public key only");
-                s->privatekey_encrypted =
-                    ssh2_userkey_encrypted(s->keyfile, NULL);
+                s->privatekey_encrypted = ppk_encrypted_f(s->keyfile, NULL);
             } else {
                 ppl_logevent("Unable to load key (%s)", error);
                 ppl_printf(WINSCP_BOM "Unable to load key file \"%s\" (%s)\r\n",
@@ -488,7 +488,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
                 prompt_get_result(s->cur_prompt->prompts[0]);
             free_prompts(s->cur_prompt);
         } else {
-            if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE))
+            if (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat))
                 ppl_printf(WINSCP_BOM "Using username \"%s\".\r\n", s->username);
         }
         s->got_username = true;
@@ -541,7 +541,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
              * anti-spoofing header lines.
              */
             if (bufchain_size(&s->banner) &&
-                (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE))) {
+                (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat))) {
                 if (s->banner_scc) {
                     ssh2_userauth_antispoof_msg(
                         s, "Pre-authentication banner message from server:");
@@ -745,6 +745,19 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
                 /*
                  * Attempt public-key authentication using a key from Pageant.
                  */
+                s->agent_keyalg = s->agent_keys[s->agent_key_index].algorithm;
+                s->signflags = 0;
+                if (ptrlen_eq_string(s->agent_keyalg, "ssh-rsa")) {
+                    /* Try to upgrade ssh-rsa to one of the rsa-sha2-* family,
+                     * if the server has announced support for them. */
+                    if (s->ppl.bpp->ext_info_rsa_sha512_ok) {
+                        s->agent_keyalg = PTRLEN_LITERAL("rsa-sha2-512");
+                        s->signflags = SSH_AGENT_RSA_SHA2_512;
+                    } else if (s->ppl.bpp->ext_info_rsa_sha256_ok) {
+                        s->agent_keyalg = PTRLEN_LITERAL("rsa-sha2-256");
+                        s->signflags = SSH_AGENT_RSA_SHA2_256;
+                    }
+                }
 
                 s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY;
 
@@ -758,8 +771,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
                 put_stringz(s->pktout, "publickey");
                                                     /* method */
                 put_bool(s->pktout, false); /* no signature included */
-                put_stringpl(s->pktout,
-                             s->agent_keys[s->agent_key_index].algorithm);
+                put_stringpl(s->pktout, s->agent_keyalg);
                 put_stringpl(s->pktout, ptrlen_from_strbuf(
                             s->agent_keys[s->agent_key_index].blob));
                 pq_push(s->ppl.out_pq, s->pktout);
@@ -777,7 +789,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
                     ptrlen comment = ptrlen_from_strbuf(
                         s->agent_keys[s->agent_key_index].comment);
 
-                    if (flags & FLAG_VERBOSE)
+                    if (seat_verbose(s->ppl.seat))
                         ppl_printf("Authenticating with public key "
                                    "\"%.*s\" from agent\r\n",
                                    PTRLEN_PRINTF(comment));
@@ -793,8 +805,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
                     put_stringz(s->pktout, "publickey");
                                                         /* method */
                     put_bool(s->pktout, true);  /* signature included */
-                    put_stringpl(s->pktout,
-                                 s->agent_keys[s->agent_key_index].algorithm);
+                    put_stringpl(s->pktout, s->agent_keyalg);
                     put_stringpl(s->pktout, ptrlen_from_strbuf(
                             s->agent_keys[s->agent_key_index].blob));
 
@@ -809,8 +820,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
                     put_data(sigdata, s->pktout->data + 5,
                              s->pktout->length - 5);
                     put_stringsb(agentreq, sigdata);
-                    /* And finally the (zero) flags word. */
-                    put_uint32(agentreq, 0);
+                    /* And finally the flags word. */
+                    put_uint32(agentreq, s->signflags);
                     ssh2_userauth_agent_query(s, agentreq);
                     strbuf_free(agentreq);
                     crWaitUntilV(!s->auth_agent_query);
@@ -865,8 +876,25 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
                 /*
                  * Try the public key supplied in the configuration.
                  *
-                 * First, offer the public blob to see if the server is
-                 * willing to accept it.
+                 * First, try to upgrade its algorithm.
+                 */
+                if (!strcmp(s->publickey_algorithm, "ssh-rsa")) {
+                    /* Try to upgrade ssh-rsa to one of the rsa-sha2-* family,
+                     * if the server has announced support for them. */
+                    if (s->ppl.bpp->ext_info_rsa_sha512_ok) {
+                        sfree(s->publickey_algorithm);
+                        s->publickey_algorithm = dupstr("rsa-sha2-512");
+                        s->signflags = SSH_AGENT_RSA_SHA2_512;
+                    } else if (s->ppl.bpp->ext_info_rsa_sha256_ok) {
+                        sfree(s->publickey_algorithm);
+                        s->publickey_algorithm = dupstr("rsa-sha2-256");
+                        s->signflags = SSH_AGENT_RSA_SHA2_256;
+                    }
+                }
+
+                /*
+                 * Offer the public blob to see if the server is willing to
+                 * accept it.
                  */
                 s->pktout = ssh_bpp_new_pktout(
                     s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
@@ -894,7 +922,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
                  * Actually attempt a serious authentication using
                  * the key.
                  */
-                if (flags & FLAG_VERBOSE)
+                if (seat_verbose(s->ppl.seat))
                     ppl_printf("Authenticating with public key \"%s\"\r\n",
                                s->publickey_comment);
 
@@ -949,7 +977,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
                     /*
                      * Try decrypting the key.
                      */
-                    key = ssh2_load_userkey(s->keyfile, passphrase, &error);
+                    key = ppk_load_f(s->keyfile, passphrase, &error);
                     if (passphrase) {
                         /* burn the evidence */
                         smemclr(passphrase, strlen(passphrase));
@@ -1001,7 +1029,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
                     put_stringz(s->pktout, s->successor_layer->vt->name);
                     put_stringz(s->pktout, "publickey"); /* method */
                     put_bool(s->pktout, true); /* signature follows */
-                    put_stringz(s->pktout, ssh_key_ssh_id(key->key));
+                    put_stringz(s->pktout, s->publickey_algorithm);
                     pkblob = strbuf_new();
                     ssh_key_public_blob(key->key, BinarySink_UPCAST(pkblob));
                     put_string(s->pktout, pkblob->s, pkblob->len);
@@ -1019,8 +1047,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
                     put_data(sigdata, s->pktout->data + 5,
                              s->pktout->length - 5);
                     sigblob = strbuf_new();
-                    ssh_key_sign(key->key, ptrlen_from_strbuf(sigdata), 0,
-                                 BinarySink_UPCAST(sigblob));
+                    ssh_key_sign(key->key, ptrlen_from_strbuf(sigdata),
+                                 s->signflags, BinarySink_UPCAST(sigblob));
                     strbuf_free(sigdata);
                     ssh2_userauth_add_sigblob(
                         s, s->pktout, ptrlen_from_strbuf(pkblob),

+ 46 - 17
source/putty/sshaes.c

@@ -87,7 +87,7 @@
  * vtables: one for the pure software implementation, one using
  * hardware acceleration (if available), and a top-level one which is
  * never actually instantiated, and only contains a new() method whose
- * job is to decide whihc of the other two to return an actual
+ * job is to decide which of the other two to return an actual
  * instance of.
  */
 
@@ -109,30 +109,54 @@ struct aes_extra {
 };
 
 #define VTABLES_INNER(cid, pid, bits, name, encsuffix,                  \
-                      decsuffix, setiv, flags)                          \
+                      decsuffix, setivsuffix, flagsval)                 \
     /*WINSCP static*/ void cid##_sw##encsuffix(ssh_cipher *, void *blk, int len);  \
     /*WINSCP static*/ void cid##_sw##decsuffix(ssh_cipher *, void *blk, int len);  \
     const ssh_cipheralg ssh_##cid##_sw = {                              \
-        aes_sw_new, aes_sw_free, aes_sw_##setiv, aes_sw_setkey,         \
-        cid##_sw##encsuffix, cid##_sw##decsuffix, NULL, NULL,           \
-        pid, 16, bits, bits/8, flags, name " (unaccelerated)",          \
-        NULL, NULL };                                                   \
+        .new = aes_sw_new,                                              \
+        .free = aes_sw_free,                                            \
+        .setiv = aes_sw_##setivsuffix,                                  \
+        .setkey = aes_sw_setkey,                                        \
+        .encrypt = cid##_sw##encsuffix,                                 \
+        .decrypt = cid##_sw##decsuffix,                                 \
+        .ssh2_id = pid,                                                 \
+        .blksize = 16,                                                  \
+        .real_keybits = bits,                                           \
+        .padded_keybytes = bits/8,                                      \
+        .flags = flagsval,                                              \
+        .text_name = name " (unaccelerated)",                           \
+    };                                                                  \
                                                                         \
     /*WINSCP static*/ void cid##_hw##encsuffix(ssh_cipher *, void *blk, int len);  \
     /*WINSCP static*/ void cid##_hw##decsuffix(ssh_cipher *, void *blk, int len);  \
     const ssh_cipheralg ssh_##cid##_hw = {                              \
-        aes_hw_new, aes_hw_free, aes_hw_##setiv, aes_hw_setkey,         \
-        cid##_hw##encsuffix, cid##_hw##decsuffix, NULL, NULL,           \
-        pid, 16, bits, bits/8, flags, name HW_NAME_SUFFIX,              \
-        NULL, NULL };                                                   \
+        .new = aes_hw_new,                                              \
+        .free = aes_hw_free,                                            \
+        .setiv = aes_hw_##setivsuffix,                                  \
+        .setkey = aes_hw_setkey,                                        \
+        .encrypt = cid##_hw##encsuffix,                                 \
+        .decrypt = cid##_hw##decsuffix,                                 \
+        .ssh2_id = pid,                                                 \
+        .blksize = 16,                                                  \
+        .real_keybits = bits,                                           \
+        .padded_keybytes = bits/8,                                      \
+        .flags = flagsval,                                              \
+        .text_name = name HW_NAME_SUFFIX,                               \
+    };                                                                  \
                                                                         \
-    const struct aes_extra extra_##cid = {                              \
+    static const struct aes_extra extra_##cid = {                       \
         &ssh_##cid##_sw, &ssh_##cid##_hw };                             \
                                                                         \
     const ssh_cipheralg ssh_##cid = {                                   \
-        aes_select, NULL, NULL, NULL, NULL, NULL, NULL, NULL,           \
-        pid, 16, bits, bits/8, flags, name " (dummy selector vtable)",  \
-        NULL, &extra_##cid };                                           \
+        .new = aes_select,                                              \
+        .ssh2_id = pid,                                                 \
+        .blksize = 16,                                                  \
+        .real_keybits = bits,                                           \
+        .padded_keybytes = bits/8,                                      \
+        .flags = flagsval,                                              \
+        .text_name = name " (dummy selector vtable)",                   \
+        .extra = &extra_##cid                                           \
+    };                                                                  \
 
 #define VTABLES(keylen)                                                 \
     VTABLES_INNER(aes ## keylen ## _cbc, "aes" #keylen "-cbc",          \
@@ -147,9 +171,14 @@ VTABLES(256)
 
 static const ssh_cipheralg ssh_rijndael_lysator = {
     /* Same as aes256_cbc, but with a different protocol ID */
-    aes_select, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-    "[email protected]", 16, 256, 256/8, 0,
-    "AES-256 CBC (dummy selector vtable)", NULL, &extra_aes256_cbc
+    .new = aes_select,
+    .ssh2_id = "[email protected]",
+    .blksize = 16,
+    .real_keybits = 256,
+    .padded_keybytes = 256/8,
+    .flags = 0,
+    .text_name = "AES-256 CBC (dummy selector vtable)",
+    .extra = &extra_aes256_cbc,
 };
 
 static const ssh_cipheralg *const aes_list[] = {

+ 24 - 10
source/putty/ssharcf.c

@@ -104,19 +104,33 @@ static void arcfour_ssh2_block(ssh_cipher *cipher, void *blk, int len)
 }
 
 const ssh_cipheralg ssh_arcfour128_ssh2 = {
-    arcfour_new, arcfour_free, arcfour_ssh2_setiv, arcfour_ssh2_setkey,
-    arcfour_ssh2_block, arcfour_ssh2_block, NULL, NULL,
-    "arcfour128",
-    1, 128, 16, 0, "Arcfour-128",
-    NULL
+    .new = arcfour_new,
+    .free = arcfour_free,
+    .setiv = arcfour_ssh2_setiv,
+    .setkey = arcfour_ssh2_setkey,
+    .encrypt = arcfour_ssh2_block,
+    .decrypt = arcfour_ssh2_block,
+    .ssh2_id = "arcfour128",
+    .blksize = 1,
+    .real_keybits = 128,
+    .padded_keybytes = 16,
+    .flags = 0,
+    .text_name = "Arcfour-128",
 };
 
 const ssh_cipheralg ssh_arcfour256_ssh2 = {
-    arcfour_new, arcfour_free, arcfour_ssh2_setiv, arcfour_ssh2_setkey,
-    arcfour_ssh2_block, arcfour_ssh2_block, NULL, NULL,
-    "arcfour256",
-    1, 256, 32, 0, "Arcfour-256",
-    NULL
+    .new = arcfour_new,
+    .free = arcfour_free,
+    .setiv = arcfour_ssh2_setiv,
+    .setkey = arcfour_ssh2_setkey,
+    .encrypt = arcfour_ssh2_block,
+    .decrypt = arcfour_ssh2_block,
+    .ssh2_id = "arcfour256",
+    .blksize = 1,
+    .real_keybits = 256,
+    .padded_keybytes = 32,
+    .flags = 0,
+    .text_name = "Arcfour-256",
 };
 
 static const ssh_cipheralg *const arcfour_list[] = {

+ 593 - 0
source/putty/sshargon2.c

@@ -0,0 +1,593 @@
+/*
+ * Implementation of the Argon2 password hash function.
+ *
+ * My sources for the algorithm description and test vectors (the latter in
+ * test/cryptsuite.py) were the reference implementation on Github, and also
+ * the Internet-Draft description:
+ *
+ *   https://github.com/P-H-C/phc-winner-argon2
+ *   https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-argon2-13
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "marshal.h"
+
+/* ----------------------------------------------------------------------
+ * Argon2 uses data marshalling rules similar to SSH but with 32-bit integers
+ * stored little-endian. Start with some local BinarySink routines for storing
+ * a uint32 and a string in that fashion.
+ */
+
+static void BinarySink_put_uint32_le(BinarySink *bs, unsigned long val)
+{
+    unsigned char data[4];
+    PUT_32BIT_LSB_FIRST(data, val);
+    bs->write(bs, data, sizeof(data));
+}
+
+static void BinarySink_put_stringpl_le(BinarySink *bs, ptrlen pl)
+{
+    /* Check that the string length fits in a uint32, without doing a
+     * potentially implementation-defined shift of more than 31 bits */
+    assert((pl.len >> 31) < 2);
+
+    BinarySink_put_uint32_le(bs, pl.len);
+    bs->write(bs, pl.ptr, pl.len);
+}
+
+#define put_uint32_le(bs, val) \
+    BinarySink_put_uint32_le(BinarySink_UPCAST(bs), val)
+#define put_stringpl_le(bs, val) \
+    BinarySink_put_stringpl_le(BinarySink_UPCAST(bs), val)
+
+/* ----------------------------------------------------------------------
+ * Argon2 defines a hash-function family that's an extension of BLAKE2b to
+ * generate longer output digests, by repeatedly outputting half of a BLAKE2
+ * hash output and then re-hashing the whole thing until there are 64 or fewer
+ * bytes left to output. The spec calls this H' (a variant of the original
+ * hash it calls H, which is the unmodified BLAKE2b).
+ */
+
+static ssh_hash *hprime_new(unsigned length)
+{
+    ssh_hash *h = blake2b_new_general(length > 64 ? 64 : length);
+    put_uint32_le(h, length);
+    return h;
+}
+
+static void hprime_final(ssh_hash *h, unsigned length, void *vout)
+{
+    uint8_t *out = (uint8_t *)vout;
+
+    while (length > 64) {
+        uint8_t hashbuf[64];
+        ssh_hash_final(h, hashbuf);
+
+        memcpy(out, hashbuf, 32);
+        out += 32;
+        length -= 32;
+
+        h = blake2b_new_general(length > 64 ? 64 : length);
+        put_data(h, hashbuf, 64);
+
+        smemclr(hashbuf, sizeof(hashbuf));
+    }
+
+    ssh_hash_final(h, out);
+}
+
+/* Externally visible entry point for the long hash function. This is only
+ * used by testcrypt, so it would be overkill to set it up like a proper
+ * ssh_hash. */
+strbuf *argon2_long_hash(unsigned length, ptrlen data)
+{
+    ssh_hash *h = hprime_new(length);
+    put_datapl(h, data);
+    { // WINSCP
+    strbuf *out = strbuf_new();
+    hprime_final(h, length, strbuf_append(out, length));
+    return out;
+    } // WINSCP
+}
+
+/* ----------------------------------------------------------------------
+ * Argon2's own mixing function G, which operates on 1Kb blocks of data.
+ *
+ * The definition of G in the spec takes two 1Kb blocks as input and produces
+ * a 1Kb output block. The first thing that happens to the input blocks is
+ * that they get XORed together, and then only the XOR output is used, so you
+ * could perfectly well regard G as a 1Kb->1Kb function.
+ */
+
+static inline uint64_t ror(uint64_t x, unsigned rotation)
+{
+#pragma option push -w-ngu // WINSCP
+    unsigned lshift = 63 & -rotation, rshift = 63 & rotation;
+#pragma option pop // WINSCP
+    return (x << lshift) | (x >> rshift);
+}
+
+static inline uint64_t trunc32(uint64_t x)
+{
+    return x & 0xFFFFFFFF;
+}
+
+/* Internal function similar to the BLAKE2b round, which mixes up four 64-bit
+ * words */
+static inline void GB(uint64_t *a, uint64_t *b, uint64_t *c, uint64_t *d)
+{
+    *a += *b + 2 * trunc32(*a) * trunc32(*b);
+    *d = ror(*d ^ *a, 32);
+    *c += *d + 2 * trunc32(*c) * trunc32(*d);
+    *b = ror(*b ^ *c, 24);
+    *a += *b + 2 * trunc32(*a) * trunc32(*b);
+    *d = ror(*d ^ *a, 16);
+    *c += *d + 2 * trunc32(*c) * trunc32(*d);
+    *b = ror(*b ^ *c, 63);
+}
+
+/* Higher-level internal function which mixes up sixteen 64-bit words. This is
+ * applied to different subsets of the 128 words in a kilobyte block, and the
+ * API here is designed to make it easy to apply in the circumstances the spec
+ * requires. In every call, the sixteen words form eight pairs adjacent in
+ * memory, whose addresses are in arithmetic progression. So the 16 input
+ * words are in[0], in[1], in[instep], in[instep+1], ..., in[7*instep],
+ * in[7*instep+1], and the 16 output words similarly. */
+static inline void P(uint64_t *out, unsigned outstep,
+                     uint64_t *in, unsigned instep)
+{
+    unsigned i; // WINSCP
+    for (i = 0; i < 8; i++) {
+        out[i*outstep] = in[i*instep];
+        out[i*outstep+1] = in[i*instep+1];
+    }
+
+    GB(out+0*outstep+0, out+2*outstep+0, out+4*outstep+0, out+6*outstep+0);
+    GB(out+0*outstep+1, out+2*outstep+1, out+4*outstep+1, out+6*outstep+1);
+    GB(out+1*outstep+0, out+3*outstep+0, out+5*outstep+0, out+7*outstep+0);
+    GB(out+1*outstep+1, out+3*outstep+1, out+5*outstep+1, out+7*outstep+1);
+
+    GB(out+0*outstep+0, out+2*outstep+1, out+5*outstep+0, out+7*outstep+1);
+    GB(out+0*outstep+1, out+3*outstep+0, out+5*outstep+1, out+6*outstep+0);
+    GB(out+1*outstep+0, out+3*outstep+1, out+4*outstep+0, out+6*outstep+1);
+    GB(out+1*outstep+1, out+2*outstep+0, out+4*outstep+1, out+7*outstep+0);
+}
+
+/* The full G function, taking input blocks X and Y. The result of G is most
+ * often XORed into an existing output block, so this API is designed with
+ * that in mind: the mixing function's output is always XORed into whatever
+ * 1Kb of data is already at 'out'. */
+static void G_xor(uint8_t *out, const uint8_t *X, const uint8_t *Y)
+{
+    uint64_t R[128], Q[128], Z[128];
+
+    unsigned i; // WINSCP
+    for (i = 0; i < 128; i++)
+        R[i] = GET_64BIT_LSB_FIRST(X + 8*i) ^ GET_64BIT_LSB_FIRST(Y + 8*i);
+
+    for (i = 0; i < 8; i++) // WINSCP
+        P(Q+16*i, 2, R+16*i, 2);
+
+    for (i = 0; i < 8; i++) // WINSCP
+        P(Z+2*i, 16, Q+2*i, 16);
+
+    for (i = 0; i < 128; i++) // WINSCP
+        PUT_64BIT_LSB_FIRST(out + 8*i,
+                            GET_64BIT_LSB_FIRST(out + 8*i) ^ R[i] ^ Z[i]);
+
+    smemclr(R, sizeof(R));
+    smemclr(Q, sizeof(Q));
+    smemclr(Z, sizeof(Z));
+}
+
+/* ----------------------------------------------------------------------
+ * The main Argon2 function.
+ */
+
+static void argon2_internal(uint32_t p, uint32_t T, uint32_t m, uint32_t t,
+                            uint32_t y, ptrlen P, ptrlen S, ptrlen K, ptrlen X,
+                            uint8_t *out)
+{
+    /*
+     * Start by hashing all the input data together: the four string arguments
+     * (password P, salt S, optional secret key K, optional associated data
+     * X), plus all the parameters for the function's memory and time usage.
+     *
+     * The output of this hash is the sole input to the subsequent mixing
+     * step: Argon2 does not preserve any more entropy from the inputs, it
+     * just makes it extra painful to get the final answer.
+     */
+    uint8_t h0[64];
+    {
+        ssh_hash *h = blake2b_new_general(64);
+        put_uint32_le(h, p);
+        put_uint32_le(h, T);
+        put_uint32_le(h, m);
+        put_uint32_le(h, t);
+        put_uint32_le(h, 0x13);        /* hash function version number */
+        put_uint32_le(h, y);
+        put_stringpl_le(h, P);
+        put_stringpl_le(h, S);
+        put_stringpl_le(h, K);
+        put_stringpl_le(h, X);
+        ssh_hash_final(h, h0);
+    }
+
+    { // WINSCP
+    struct blk { uint8_t data[1024]; };
+
+    /*
+     * Array of 1Kb blocks. The total size is (approximately) m, the
+     * caller-specified parameter for how much memory to use; the blocks are
+     * regarded as a rectangular array of p rows ('lanes') by q columns, where
+     * p is the 'parallelism' input parameter (the lanes can be processed
+     * concurrently up to a point) and q is whatever makes the product pq come
+     * to m.
+     *
+     * Additionally, each row is divided into four equal 'segments', which are
+     * important to the way the algorithm decides which blocks to use as input
+     * to each step of the function.
+     *
+     * The term 'slice' refers to a whole set of vertically aligned segments,
+     * i.e. slice 0 is the whole left quarter of the array, and slice 3 the
+     * whole right quarter.
+     */
+    size_t SL = m / (4*p); /* segment length: # of 1Kb blocks in a segment */
+    size_t q = 4 * SL;     /* width of the array: 4 segments times SL */
+    size_t mprime = q * p; /* total size of the array, approximately m */
+
+    /* Allocate the memory. */
+    struct blk *B = snewn(mprime, struct blk);
+    memset(B, 0, mprime * sizeof(struct blk));
+
+    /*
+     * Initial setup: fill the first two full columns of the array with data
+     * expanded from the starting hash h0. Each block is the result of using
+     * the long-output hash function H' to hash h0 itself plus the block's
+     * coordinates in the array.
+     */
+    { // WINSCP
+    size_t i; // WINSCP
+    for (i = 0; i < p; i++) {
+        ssh_hash *h = hprime_new(1024);
+        put_data(h, h0, 64);
+        put_uint32_le(h, 0);
+        put_uint32_le(h, i);
+        hprime_final(h, 1024, B[i].data);
+    }
+    for (i = 0; i < p; i++) { // WINSCP
+        ssh_hash *h = hprime_new(1024);
+        put_data(h, h0, 64);
+        put_uint32_le(h, 1);
+        put_uint32_le(h, i);
+        hprime_final(h, 1024, B[i+p].data);
+    }
+
+    /*
+     * Declarations for the main loop.
+     *
+     * The basic structure of the main loop is going to involve processing the
+     * array one whole slice (vertically divided quarter) at a time. Usually
+     * we'll write a new value into every single block in the slice, except
+     * that in the initial slice on the first pass, we've already written
+     * values into the first two columns during the initial setup above. So
+     * 'jstart' indicates the starting index in each segment we process; it
+     * starts off as 2 so that we don't overwrite the inital setup, and then
+     * after the first slice is done, we set it to 0, and it stays there.
+     *
+     * d_mode indicates whether we're being data-dependent (true) or
+     * data-independent (false). In the hybrid Argon2id mode, we start off
+     * independent, and then once we've mixed things up enough, switch over to
+     * dependent mode to force long serial chains of computation.
+     */
+    { // WINSCP
+    size_t jstart = 2;
+    bool d_mode = (y == 0);
+    struct blk out2i, tmp2i, in2i;
+
+    /* Outermost loop: t whole passes from left to right over the array */
+    size_t pass; // WINSCP
+    for (pass = 0; pass < t; pass++) {
+
+        /* Within that, we process the array in its four main slices */
+        unsigned slice; // WINSCP
+        for (slice = 0; slice < 4; slice++) {
+
+            /* In Argon2id mode, if we're half way through the first pass,
+             * this is the moment to switch d_mode from false to true */
+            if (pass == 0 && slice == 2 && y == 2)
+                d_mode = true;
+
+            /* Loop over every segment in the slice (i.e. every row). So i is
+             * the y-coordinate of each block we process. */
+            { // WINSCP
+            size_t i; // WINSCP
+            for (i = 0; i < p; i++) {
+
+                /* And within that segment, process the blocks from left to
+                 * right, starting at 'jstart' (usually 0, but 2 in the first
+                 * slice). */
+                size_t jpre; // WINSCP
+                for (jpre = jstart; jpre < SL; jpre++) {
+
+                    /* j is the x-coordinate of each block we process, made up
+                     * of the slice number and the index 'jpre' within the
+                     * segment. */
+                    size_t j = slice * SL + jpre;
+
+                    /* jm1 is j-1 (mod q) */
+                    uint32_t jm1 = (j == 0 ? q-1 : j-1);
+
+                    /*
+                     * Construct two 32-bit pseudorandom integers J1 and J2.
+                     * This is the part of the algorithm that varies between
+                     * the data-dependent and independent modes.
+                     */
+                    uint32_t J1, J2;
+                    if (d_mode) {
+                        /*
+                         * Data-dependent: grab the first 64 bits of the block
+                         * to the left of this one.
+                         */
+                        J1 = GET_32BIT_LSB_FIRST(B[i + p * jm1].data);
+                        J2 = GET_32BIT_LSB_FIRST(B[i + p * jm1].data + 4);
+                    } else {
+                        /*
+                         * Data-independent: generate pseudorandom data by
+                         * hashing a sequence of preimage blocks that include
+                         * all our input parameters, plus the coordinates of
+                         * this point in the algorithm (array position and
+                         * pass number) to make all the hash outputs distinct.
+                         *
+                         * The hash we use is G itself, applied twice. So we
+                         * generate 1Kb of data at a time, which is enough for
+                         * 128 (J1,J2) pairs. Hence we only need to do the
+                         * hashing if our index within the segment is a
+                         * multiple of 128, or if we're at the very start of
+                         * the algorithm (in which case we started at 2 rather
+                         * than 0). After that we can just keep picking data
+                         * out of our most recent hash output.
+                         */
+                        if (jpre == jstart || jpre % 128 == 0) {
+                            /*
+                             * Hash preimage is mostly zeroes, with a
+                             * collection of assorted integer values we had
+                             * anyway.
+                             */
+                            memset(in2i.data, 0, sizeof(in2i.data));
+                            PUT_64BIT_LSB_FIRST(in2i.data +  0, pass);
+                            PUT_64BIT_LSB_FIRST(in2i.data +  8, i);
+                            PUT_64BIT_LSB_FIRST(in2i.data + 16, slice);
+                            PUT_64BIT_LSB_FIRST(in2i.data + 24, mprime);
+                            PUT_64BIT_LSB_FIRST(in2i.data + 32, t);
+                            PUT_64BIT_LSB_FIRST(in2i.data + 40, y);
+                            PUT_64BIT_LSB_FIRST(in2i.data + 48, jpre / 128 + 1);
+
+                            /*
+                             * Now apply G twice to generate the hash output
+                             * in out2i.
+                             */
+                            memset(tmp2i.data, 0, sizeof(tmp2i.data));
+                            G_xor(tmp2i.data, tmp2i.data, in2i.data);
+                            memset(out2i.data, 0, sizeof(out2i.data));
+                            G_xor(out2i.data, out2i.data, tmp2i.data);
+                        }
+
+                        /*
+                         * Extract J1 and J2 from the most recent hash output
+                         * (whether we've just computed it or not).
+                         */
+                        J1 = GET_32BIT_LSB_FIRST(
+                            out2i.data + 8 * (jpre % 128));
+                        J2 = GET_32BIT_LSB_FIRST(
+                            out2i.data + 8 * (jpre % 128) + 4);
+                    }
+
+                    /*
+                     * Now convert J1 and J2 into the index of an existing
+                     * block of the array to use as input to this step. This
+                     * is fairly fiddly.
+                     *
+                     * The easy part: the y-coordinate of the input block is
+                     * obtained by reducing J2 mod p, except that at the very
+                     * start of the algorithm (processing the first slice on
+                     * the first pass) we simply use the same y-coordinate as
+                     * our output block.
+                     *
+                     * Note that it's safe to use the ordinary % operator
+                     * here, without any concern for timing side channels: in
+                     * data-independent mode J2 is not correlated to any
+                     * secrets, and in data-dependent mode we're going to be
+                     * giving away side-channel data _anyway_ when we use it
+                     * as an array index (and by assumption we don't care,
+                     * because it's already massively randomised from the real
+                     * inputs).
+                     */
+                    { // WINSCP
+                    uint32_t index_l = (pass == 0 && slice == 0) ? i : J2 % p;
+
+                    /*
+                     * The hard part: which block in this array row do we use?
+                     *
+                     * First, we decide what the possible candidates are. This
+                     * requires some case analysis, and depends on whether the
+                     * array row is the same one we're writing into or not.
+                     *
+                     * If it's not the same row: we can't use any block from
+                     * the current slice (because the segments within a slice
+                     * have to be processable in parallel, so in a concurrent
+                     * implementation those blocks are potentially in the
+                     * process of being overwritten by other threads). But the
+                     * other three slices are fair game, except that in the
+                     * first pass, slices to the right of us won't have had
+                     * any values written into them yet at all.
+                     *
+                     * If it is the same row, we _are_ allowed to use blocks
+                     * from the current slice, but only the ones before our
+                     * current position.
+                     *
+                     * In both cases, we also exclude the individual _column_
+                     * just to the left of the current one. (The block
+                     * immediately to our left is going to be the _other_
+                     * input to G, but the spec also says that we avoid that
+                     * column even in a different row.)
+                     *
+                     * All of this means that we end up choosing from a
+                     * cyclically contiguous interval of blocks within this
+                     * lane, but the start and end points require some thought
+                     * to get them right.
+                     */
+
+                    /* Start position is the beginning of the _next_ slice
+                     * (containing data from the previous pass), unless we're
+                     * on pass 0, where the start position has to be 0. */
+                    uint32_t Wstart = (pass == 0 ? 0 : (slice + 1) % 4 * SL);
+
+                    /* End position splits up by cases. */
+                    uint32_t Wend;
+                    if (index_l == i) {
+                        /* Same lane as output: we can use anything up to (but
+                         * not including) the block immediately left of us. */
+                        Wend = jm1;
+                    } else {
+                        /* Different lane from output: we can use anything up
+                         * to the previous slice boundary, or one less than
+                         * that if we're at the very left edge of our slice
+                         * right now. */
+                        Wend = SL * slice;
+                        if (jpre == 0)
+                            Wend = (Wend + q-1) % q;
+                    }
+
+                    /* Total number of blocks available to choose from */
+                    { // WINSCP
+                    uint32_t Wsize = (Wend + q - Wstart) % q;
+
+                    /* Fiddly computation from the spec that chooses from the
+                     * available blocks, in a deliberately non-uniform
+                     * fashion, using J1 as pseudorandom input data. Output is
+                     * zz which is the index within our contiguous interval. */
+                    uint32_t x = ((uint64_t)J1 * J1) >> 32;
+                    uint32_t y = ((uint64_t)Wsize * x) >> 32;
+                    uint32_t zz = Wsize - 1 - y;
+
+                    /* And index_z is the actual x coordinate of the block we
+                     * want. */
+                    uint32_t index_z = (Wstart + zz) % q;
+
+                    /* Phew! Combine that block with the one immediately to
+                     * our left, and XOR over the top of whatever is already
+                     * in our current output block. */
+                    G_xor(B[i + p * j].data, B[i + p * jm1].data,
+                          B[index_l + p * index_z].data);
+                    } // WINSCP
+                    } // WINSCP
+                }
+            }
+
+            /* We've finished processing a slice. Reset jstart to 0. It will
+             * onily _not_ have been 0 if this was pass 0 slice 0, in which
+             * case it still had its initial value of 2 to avoid the starting
+             * data. */
+            jstart = 0;
+            } // WINSCP
+        }
+    }
+
+    /*
+     * The main output is all done. Final output works by taking the XOR of
+     * all the blocks in the rightmost column of the array, and then using
+     * that as input to our long hash H'. The output of _that_ is what we
+     * deliver to the caller.
+     */
+
+    { // WINSCP
+    struct blk C = B[p * (q-1)];
+    size_t i; // WINSCP
+    for (i = 1; i < p; i++)
+        memxor(C.data, C.data, B[i + p * (q-1)].data, 1024);
+
+    {
+        ssh_hash *h = hprime_new(T);
+        put_data(h, C.data, 1024);
+        hprime_final(h, T, out);
+    }
+
+    /*
+     * Clean up.
+     */
+    smemclr(out2i.data, sizeof(out2i.data));
+    smemclr(tmp2i.data, sizeof(tmp2i.data));
+    smemclr(in2i.data, sizeof(in2i.data));
+    smemclr(C.data, sizeof(C.data));
+    smemclr(B, mprime * sizeof(struct blk));
+    sfree(B);
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+/*
+ * Wrapper function that appends to a strbuf (which sshpubk.c will want).
+ */
+void argon2(Argon2Flavour flavour, uint32_t mem, uint32_t passes,
+            uint32_t parallel, uint32_t taglen,
+            ptrlen P, ptrlen S, ptrlen K, ptrlen X, strbuf *out)
+{
+    argon2_internal(parallel, taglen, mem, passes, flavour,
+                    P, S, K, X, strbuf_append(out, taglen));
+}
+
+/*
+ * Wrapper function which dynamically chooses the number of passes to run in
+ * order to hit an approximate total amount of CPU time. Writes the result
+ * into 'passes'.
+ */
+void argon2_choose_passes(
+    Argon2Flavour flavour, uint32_t mem,
+    uint32_t milliseconds, uint32_t *passes,
+    uint32_t parallel, uint32_t taglen,
+    ptrlen P, ptrlen S, ptrlen K, ptrlen X,
+    strbuf *out)
+{
+    unsigned long desired_time = (TICKSPERSEC * milliseconds) / 1000;
+
+    /*
+     * We only need the time taken to be approximately right, so we
+     * scale up the number of passes geometrically, which avoids
+     * taking O(t^2) time to find a pass count taking time t.
+     *
+     * Using the Fibonacci numbers is slightly nicer than the obvious
+     * approach of powers of 2, because it's still very easy to
+     * compute, and grows less fast (powers of 1.6 instead of 2), so
+     * you get just a touch more precision.
+     */
+    uint32_t a = 1, b = 1;
+
+    while (true) {
+        unsigned long start_time = GETTICKCOUNT();
+        argon2(flavour, mem, b, parallel, taglen, P, S, K, X, out);
+        { // WINSCP
+        unsigned long ticks = GETTICKCOUNT() - start_time;
+
+        /* But just in case computers get _too_ fast, we have to cap
+         * the growth before it gets past the uint32_t upper bound! So
+         * if computing a+b would overflow, stop here. */
+
+        if (ticks >= desired_time || a > (uint32_t)~b) {
+            *passes = b;
+            return;
+        } else {
+            strbuf_clear(out);
+
+            /* Next Fibonacci number: replace (a, b) with (b, a+b) */
+            b += a;
+            a = b - a;
+        }
+        } // WINSCP
+    }
+}

+ 5 - 7
source/putty/sshauxcrypt.c

@@ -11,14 +11,12 @@
 
 #include "ssh.h"
 
-static ssh_cipher *aes256_pubkey_cipher(const void *key)
+static ssh_cipher *aes256_pubkey_cipher(const void *key, const void *iv)
 {
     /*
      * PuTTY's own .PPK format for SSH-2 private key files is
      * encrypted with 256-bit AES in CBC mode.
      */
-    char iv[16];
-    memset(iv, 0, 16);
     { // WINSCP
     ssh_cipher *cipher = ssh_cipher_new(&ssh_aes256_cbc);
     ssh_cipher_setkey(cipher, key);
@@ -27,16 +25,16 @@ static ssh_cipher *aes256_pubkey_cipher(const void *key)
     } // WINSCP
 }
 
-void aes256_encrypt_pubkey(const void *key, void *blk, int len)
+void aes256_encrypt_pubkey(const void *key, const void *iv, void *blk, int len)
 {
-    ssh_cipher *c = aes256_pubkey_cipher(key);
+    ssh_cipher *c = aes256_pubkey_cipher(key, iv);
     ssh_cipher_encrypt(c, blk, len);
     ssh_cipher_free(c);
 }
 
-void aes256_decrypt_pubkey(const void *key, void *blk, int len)
+void aes256_decrypt_pubkey(const void *key, const void *iv, void *blk, int len)
 {
-    ssh_cipher *c = aes256_pubkey_cipher(key);
+    ssh_cipher *c = aes256_pubkey_cipher(key, iv);
     ssh_cipher_decrypt(c, blk, len);
     ssh_cipher_free(c);
 }

+ 242 - 0
source/putty/sshblake2.c

@@ -0,0 +1,242 @@
+/*
+ * BLAKE2 (RFC 7693) implementation for PuTTY.
+ *
+ * The BLAKE2 hash family includes BLAKE2s, in which the hash state is
+ * operated on as a collection of 32-bit integers, and BLAKE2b, based
+ * on 64-bit integers. At present this code implements BLAKE2b only.
+ */
+
+#include <assert.h>
+#include "ssh.h"
+
+static inline uint64_t ror(uint64_t x, unsigned rotation)
+{
+#pragma option push -w-ngu // WINSCP
+    unsigned lshift = 63 & -rotation, rshift = 63 & rotation;
+#pragma option pop // WINSCP
+    return (x << lshift) | (x >> rshift);
+}
+
+/* RFC 7963 section 2.1 */
+enum { R1 = 32, R2 = 24, R3 = 16, R4 = 63 };
+
+/* RFC 7693 section 2.6 */
+static const uint64_t iv[] = {
+    // WINSCP (ULL)
+    0x6a09e667f3bcc908ULL,                /* floor(2^64 * frac(sqrt(2)))  */
+    0xbb67ae8584caa73bULL,                /* floor(2^64 * frac(sqrt(3)))  */
+    0x3c6ef372fe94f82bULL,                /* floor(2^64 * frac(sqrt(5)))  */
+    0xa54ff53a5f1d36f1ULL,                /* floor(2^64 * frac(sqrt(7)))  */
+    0x510e527fade682d1ULL,                /* floor(2^64 * frac(sqrt(11))) */
+    0x9b05688c2b3e6c1fULL,                /* floor(2^64 * frac(sqrt(13))) */
+    0x1f83d9abfb41bd6bULL,                /* floor(2^64 * frac(sqrt(17))) */
+    0x5be0cd19137e2179ULL,                /* floor(2^64 * frac(sqrt(19))) */
+};
+
+/* RFC 7693 section 2.7 */
+static const unsigned char sigma[][16] = {
+    { 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15},
+    {14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3},
+    {11,  8, 12,  0,  5,  2, 15, 13, 10, 14,  3,  6,  7,  1,  9,  4},
+    { 7,  9,  3,  1, 13, 12, 11, 14,  2,  6,  5, 10,  4,  0, 15,  8},
+    { 9,  0,  5,  7,  2,  4, 10, 15, 14,  1, 11, 12,  6,  8,  3, 13},
+    { 2, 12,  6, 10,  0, 11,  8,  3,  4, 13,  7,  5, 15, 14,  1,  9},
+    {12,  5,  1, 15, 14, 13,  4, 10,  0,  7,  6,  3,  9,  2,  8, 11},
+    {13, 11,  7, 14, 12,  1,  3,  9,  5,  0, 15,  4,  8,  6,  2, 10},
+    { 6, 15, 14,  9, 11,  3,  0,  8, 12,  2, 13,  7,  1,  4, 10,  5},
+    {10,  2,  8,  4,  7,  6,  1,  5, 15, 11,  9, 14,  3, 12, 13,  0},
+    /* This array recycles if you have more than 10 rounds. BLAKE2b
+     * has 12, so we repeat the first two rows again. */
+    { 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15},
+    {14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3},
+};
+
+static inline void g_half(uint64_t v[16], unsigned a, unsigned b, unsigned c,
+                          unsigned d, uint64_t x, unsigned r1, unsigned r2)
+{
+    v[a] += v[b] + x;
+    v[d] ^= v[a];
+    v[d] = ror(v[d], r1);
+    v[c] += v[d];
+    v[b] ^= v[c];
+    v[b] = ror(v[b], r2);
+}
+
+static inline void g(uint64_t v[16], unsigned a, unsigned b, unsigned c,
+                     unsigned d, uint64_t x, uint64_t y)
+{
+    g_half(v, a, b, c, d, x, R1, R2);
+    g_half(v, a, b, c, d, y, R3, R4);
+}
+
+static inline void f(uint64_t h[8], uint64_t m[16], uint64_t offset_hi,
+                     uint64_t offset_lo, unsigned final)
+{
+    uint64_t v[16];
+    memcpy(v, h, 8 * sizeof(*v));
+    memcpy(v + 8, iv, 8 * sizeof(*v));
+    v[12] ^= offset_lo;
+    v[13] ^= offset_hi;
+    v[14] ^= -(uint64_t)final;
+    { // WINSCP
+    unsigned round; // WINSCP
+    for (round = 0; round < 12; round++) {
+        const unsigned char *s = sigma[round];
+        g(v,  0,  4,  8, 12, m[s[ 0]], m[s[ 1]]);
+        g(v,  1,  5,  9, 13, m[s[ 2]], m[s[ 3]]);
+        g(v,  2,  6, 10, 14, m[s[ 4]], m[s[ 5]]);
+        g(v,  3,  7, 11, 15, m[s[ 6]], m[s[ 7]]);
+        g(v,  0,  5, 10, 15, m[s[ 8]], m[s[ 9]]);
+        g(v,  1,  6, 11, 12, m[s[10]], m[s[11]]);
+        g(v,  2,  7,  8, 13, m[s[12]], m[s[13]]);
+        g(v,  3,  4,  9, 14, m[s[14]], m[s[15]]);
+    }
+    { // WINSCP
+    unsigned i; // WINSCP
+    for (i = 0; i < 8; i++)
+        h[i] ^= v[i] ^ v[i+8];
+    smemclr(v, sizeof(v));
+    } // WINSCP
+    } // WINSCP
+}
+
+static inline void f_outer(uint64_t h[8], uint8_t blk[128], uint64_t offset_hi,
+                           uint64_t offset_lo, unsigned final)
+{
+    uint64_t m[16];
+    unsigned i; // WINSCP
+    for (i = 0; i < 16; i++)
+        m[i] = GET_64BIT_LSB_FIRST(blk + 8*i);
+    f(h, m, offset_hi, offset_lo, final);
+    smemclr(m, sizeof(m));
+}
+
+typedef struct blake2b {
+    uint64_t h[8];
+    unsigned hashlen;
+
+    uint8_t block[128];
+    size_t used;
+    uint64_t lenhi, lenlo;
+
+    BinarySink_IMPLEMENTATION;
+    ssh_hash hash;
+} blake2b;
+
+static void blake2b_write(BinarySink *bs, const void *vp, size_t len);
+
+static ssh_hash *blake2b_new_inner(unsigned hashlen)
+{
+    assert(hashlen <= ssh_blake2b.hlen);
+
+    { // WINSCP
+    blake2b *s = snew(blake2b);
+    s->hash.vt = &ssh_blake2b;
+    s->hashlen = hashlen;
+    BinarySink_INIT(s, blake2b_write);
+    BinarySink_DELEGATE_INIT(&s->hash, s);
+    return &s->hash;
+    } // WINSCP
+}
+
+static ssh_hash *blake2b_new(const ssh_hashalg *alg)
+{
+    return blake2b_new_inner(alg->hlen);
+}
+
+ssh_hash *blake2b_new_general(unsigned hashlen)
+{
+    ssh_hash *h = blake2b_new_inner(hashlen);
+    ssh_hash_reset(h);
+    return h;
+}
+
+static void blake2b_reset(ssh_hash *hash)
+{
+    blake2b *s = container_of(hash, blake2b, hash);
+
+    /* Initialise the hash to the standard IV */
+    memcpy(s->h, iv, sizeof(s->h));
+
+    /* XOR in the parameters: secret key length (here always 0) in
+     * byte 1, and hash length in byte 0. */
+    s->h[0] ^= 0x01010000 ^ s->hashlen;
+
+    s->used = 0;
+    s->lenhi = s->lenlo = 0;
+}
+
+static void blake2b_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+    blake2b *copy = container_of(hcopy, blake2b, hash);
+    blake2b *orig = container_of(horig, blake2b, hash);
+
+    memcpy(copy, orig, sizeof(*copy));
+    BinarySink_COPIED(copy);
+    BinarySink_DELEGATE_INIT(&copy->hash, copy);
+}
+
+static void blake2b_free(ssh_hash *hash)
+{
+    blake2b *s = container_of(hash, blake2b, hash);
+
+    smemclr(s, sizeof(*s));
+    sfree(s);
+}
+
+static void blake2b_write(BinarySink *bs, const void *vp, size_t len)
+{
+    blake2b *s = BinarySink_DOWNCAST(bs, blake2b);
+    const uint8_t *p = vp;
+
+    while (len > 0) {
+        if (s->used == sizeof(s->block)) {
+            f_outer(s->h, s->block, s->lenhi, s->lenlo, 0);
+            s->used = 0;
+        }
+
+        { // WINSCP
+        size_t chunk = sizeof(s->block) - s->used;
+        if (chunk > len)
+            chunk = len;
+
+        memcpy(s->block + s->used, p, chunk);
+        s->used += chunk;
+        p += chunk;
+        len -= chunk;
+
+        s->lenlo += chunk;
+        s->lenhi += (s->lenlo < chunk);
+        } // WINSCP
+    }
+}
+
+static void blake2b_digest(ssh_hash *hash, uint8_t *digest)
+{
+    blake2b *s = container_of(hash, blake2b, hash);
+
+    memset(s->block + s->used, 0, sizeof(s->block) - s->used);
+    f_outer(s->h, s->block, s->lenhi, s->lenlo, 1);
+
+    { // WINSCP
+    uint8_t hash_pre[128];
+    unsigned i; // WINSCP
+    for (i = 0; i < 8; i++)
+        PUT_64BIT_LSB_FIRST(hash_pre + 8*i, s->h[i]);
+    memcpy(digest, hash_pre, s->hashlen);
+    smemclr(hash_pre, sizeof(hash_pre));
+    } // WINSCP
+}
+
+const ssh_hashalg ssh_blake2b = {
+    // WINSCP
+    /*.new =*/ blake2b_new,
+    /*.reset =*/ blake2b_reset,
+    /*.copyfrom =*/ blake2b_copyfrom,
+    /*.digest =*/ blake2b_digest,
+    /*.free =*/ blake2b_free,
+    /*.hlen =*/ 64,
+    /*.blocklen =*/ 128,
+    HASHALG_NAMES_BARE("BLAKE2b-64"),
+    NULL, // WINSCP
+};

+ 35 - 18
source/putty/sshblowf.c

@@ -648,30 +648,47 @@ static void blowfish_ssh2_sdctr(ssh_cipher *cipher, void *blk, int len)
 }
 
 const ssh_cipheralg ssh_blowfish_ssh1 = {
-    blowfish_new, blowfish_free,
-    blowfish_ssh1_setiv, blowfish_ssh_setkey,
-    blowfish_ssh1_encrypt_blk, blowfish_ssh1_decrypt_blk,
-    NULL, NULL, NULL,
-    8, 128, SSH1_SESSION_KEY_LENGTH, SSH_CIPHER_IS_CBC, "Blowfish-256 CBC",
-    NULL
+    .new = blowfish_new,
+    .free = blowfish_free,
+    .setiv = blowfish_ssh1_setiv,
+    .setkey = blowfish_ssh_setkey,
+    .encrypt = blowfish_ssh1_encrypt_blk,
+    .decrypt = blowfish_ssh1_decrypt_blk,
+    .blksize = 8,
+    .real_keybits = 128,
+    .padded_keybytes = SSH1_SESSION_KEY_LENGTH,
+    .flags = SSH_CIPHER_IS_CBC,
+    .text_name = "Blowfish-256 CBC",
 };
 
 const ssh_cipheralg ssh_blowfish_ssh2 = {
-    blowfish_new, blowfish_free,
-    blowfish_ssh2_setiv, blowfish_ssh_setkey,
-    blowfish_ssh2_encrypt_blk, blowfish_ssh2_decrypt_blk, NULL, NULL,
-    "blowfish-cbc",
-    8, 128, 16, SSH_CIPHER_IS_CBC, "Blowfish-128 CBC",
-    NULL
+    .new = blowfish_new,
+    .free = blowfish_free,
+    .setiv = blowfish_ssh2_setiv,
+    .setkey = blowfish_ssh_setkey,
+    .encrypt = blowfish_ssh2_encrypt_blk,
+    .decrypt = blowfish_ssh2_decrypt_blk,
+    .ssh2_id = "blowfish-cbc",
+    .blksize = 8,
+    .real_keybits = 128,
+    .padded_keybytes = 16,
+    .flags = SSH_CIPHER_IS_CBC,
+    .text_name = "Blowfish-128 CBC",
 };
 
 const ssh_cipheralg ssh_blowfish_ssh2_ctr = {
-    blowfish_new, blowfish_free,
-    blowfish_ssh2_setiv, blowfish_ssh_setkey,
-    blowfish_ssh2_sdctr, blowfish_ssh2_sdctr, NULL, NULL,
-    "blowfish-ctr",
-    8, 256, 32, 0, "Blowfish-256 SDCTR",
-    NULL
+    .new = blowfish_new,
+    .free = blowfish_free,
+    .setiv = blowfish_ssh2_setiv,
+    .setkey = blowfish_ssh_setkey,
+    .encrypt = blowfish_ssh2_sdctr,
+    .decrypt = blowfish_ssh2_sdctr,
+    .ssh2_id = "blowfish-ctr",
+    .blksize = 8,
+    .real_keybits = 256,
+    .padded_keybytes = 32,
+    .flags = 0,
+    .text_name = "Blowfish-256 SDCTR",
 };
 
 static const ssh_cipheralg *const blowfish_list[] = {

+ 5 - 0
source/putty/sshbpp.h

@@ -5,6 +5,8 @@
 #ifndef PUTTY_SSHBPP_H
 #define PUTTY_SSHBPP_H
 
+typedef struct BinaryPacketProtocolVtable BinaryPacketProtocolVtable;
+
 struct BinaryPacketProtocolVtable {
     void (*free)(BinaryPacketProtocol *);
     void (*handle_input)(BinaryPacketProtocol *);
@@ -34,7 +36,10 @@ struct BinaryPacketProtocol {
      * the callback on out_pq. */
     IdempotentCallback ic_out_pq;
 
+    /* Information that all packet layers sharing this BPP will
+     * potentially be interested in. */
     int remote_bugs;
+    bool ext_info_rsa_sha256_ok, ext_info_rsa_sha512_ok;
 
     /* Set this if remote connection closure should not generate an
      * error message (either because it's not to be treated as an

+ 26 - 19
source/putty/sshccp.c

@@ -944,11 +944,17 @@ static const char *poly_text_name(ssh2_mac *mac)
 }
 
 const ssh2_macalg ssh2_poly1305 = {
-    poly_ssh2_new, poly_ssh2_free, poly_setkey,
-    poly_start, poly_genresult, poly_text_name,
-
-    "", "", /* Not selectable individually, just part of ChaCha20-Poly1305 */
-    16, 0,
+    .new = poly_ssh2_new,
+    .free = poly_ssh2_free,
+    .setkey = poly_setkey,
+    .start = poly_start,
+    .genresult = poly_genresult,
+    .text_name = poly_text_name,
+    .name = "",
+    .etm_name = "", /* Not selectable individually, just part of
+                     * ChaCha20-Poly1305 */
+    .len = 16,
+    .keylen = 0,
 };
 
 static ssh_cipher *ccp_new(const ssh_cipheralg *alg)
@@ -1032,20 +1038,21 @@ static void ccp_decrypt_length(ssh_cipher *cipher, void *blk, int len,
 }
 
 const ssh_cipheralg ssh2_chacha20_poly1305 = {
-
-    ccp_new,
-    ccp_free,
-    ccp_iv,
-    ccp_key,
-    ccp_encrypt,
-    ccp_decrypt,
-    ccp_encrypt_length,
-    ccp_decrypt_length,
-
-    "[email protected]",
-    1, 512, 64, SSH_CIPHER_SEPARATE_LENGTH, "ChaCha20",
-
-    &ssh2_poly1305
+    .new = ccp_new,
+    .free = ccp_free,
+    .setiv = ccp_iv,
+    .setkey = ccp_key,
+    .encrypt = ccp_encrypt,
+    .decrypt = ccp_decrypt,
+    .encrypt_length = ccp_encrypt_length,
+    .decrypt_length = ccp_decrypt_length,
+    .ssh2_id = "[email protected]",
+    .blksize = 1,
+    .real_keybits = 512,
+    .padded_keybytes = 64,
+    .flags = SSH_CIPHER_SEPARATE_LENGTH,
+    .text_name = "ChaCha20",
+    .required_mac = &ssh2_poly1305,
 };
 
 static const ssh_cipheralg *const ccp_list[] = {

+ 4 - 0
source/putty/sshchan.h

@@ -6,6 +6,8 @@
 #ifndef PUTTY_SSHCHAN_H
 #define PUTTY_SSHCHAN_H
 
+typedef struct ChannelVtable ChannelVtable;
+
 struct ChannelVtable {
     void (*free)(Channel *);
 
@@ -169,6 +171,8 @@ Channel *zombiechan_new(void);
  * implementation to talk back to.
  */
 
+typedef struct SshChannelVtable SshChannelVtable;
+
 struct SshChannelVtable {
     size_t (*write)(SshChannel *c, bool is_stderr, const void *, size_t);
     void (*write_eof)(SshChannel *c);

+ 41 - 158
source/putty/sshcommon.c

@@ -315,29 +315,29 @@ static void zombiechan_open_failure(Channel *chan, const char *);
 static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof);
 static char *zombiechan_log_close_msg(Channel *chan) { return NULL; }
 
-static const struct ChannelVtable zombiechan_channelvt = {
-    zombiechan_free,
-    zombiechan_do_nothing,             /* open_confirmation */
-    zombiechan_open_failure,
-    zombiechan_send,
-    zombiechan_do_nothing,             /* send_eof */
-    zombiechan_set_input_wanted,
-    zombiechan_log_close_msg,
-    zombiechan_want_close,
-    chan_no_exit_status,
-    chan_no_exit_signal,
-    chan_no_exit_signal_numeric,
-    chan_no_run_shell,
-    chan_no_run_command,
-    chan_no_run_subsystem,
-    chan_no_enable_x11_forwarding,
-    chan_no_enable_agent_forwarding,
-    chan_no_allocate_pty,
-    chan_no_set_env,
-    chan_no_send_break,
-    chan_no_send_signal,
-    chan_no_change_window_size,
-    chan_no_request_response,
+static const ChannelVtable zombiechan_channelvt = {
+    .free = zombiechan_free,
+    .open_confirmation = zombiechan_do_nothing,
+    .open_failed = zombiechan_open_failure,
+    .send = zombiechan_send,
+    .send_eof = zombiechan_do_nothing,
+    .set_input_wanted = zombiechan_set_input_wanted,
+    .log_close_msg = zombiechan_log_close_msg,
+    .want_close = zombiechan_want_close,
+    .rcvd_exit_status = chan_no_exit_status,
+    .rcvd_exit_signal = chan_no_exit_signal,
+    .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
+    .run_shell = chan_no_run_shell,
+    .run_command = chan_no_run_command,
+    .run_subsystem = chan_no_run_subsystem,
+    .enable_x11_forwarding = chan_no_enable_x11_forwarding,
+    .enable_agent_forwarding = chan_no_enable_agent_forwarding,
+    .allocate_pty = chan_no_allocate_pty,
+    .set_env = chan_no_set_env,
+    .send_break = chan_no_send_break,
+    .send_signal = chan_no_send_signal,
+    .change_window_size = chan_no_change_window_size,
+    .request_response = chan_no_request_response,
 };
 
 Channel *zombiechan_new(void)
@@ -381,109 +381,6 @@ static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof)
     return true;
 }
 
-/* ----------------------------------------------------------------------
- * Centralised standard methods for other channel implementations to
- * borrow.
- */
-
-void chan_remotely_opened_confirmation(Channel *chan)
-{
-    unreachable("this channel type should never receive OPEN_CONFIRMATION");
-}
-
-void chan_remotely_opened_failure(Channel *chan, const char *errtext)
-{
-    unreachable("this channel type should never receive OPEN_FAILURE");
-}
-
-bool chan_default_want_close(
-    Channel *chan, bool sent_local_eof, bool rcvd_remote_eof)
-{
-    /*
-     * Default close policy: we start initiating the CHANNEL_CLOSE
-     * procedure as soon as both sides of the channel have seen EOF.
-     */
-    return sent_local_eof && rcvd_remote_eof;
-}
-
-bool chan_no_exit_status(Channel *chan, int status)
-{
-    return false;
-}
-
-bool chan_no_exit_signal(
-    Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg)
-{
-    return false;
-}
-
-bool chan_no_exit_signal_numeric(
-    Channel *chan, int signum, bool core_dumped, ptrlen msg)
-{
-    return false;
-}
-
-bool chan_no_run_shell(Channel *chan)
-{
-    return false;
-}
-
-bool chan_no_run_command(Channel *chan, ptrlen command)
-{
-    return false;
-}
-
-bool chan_no_run_subsystem(Channel *chan, ptrlen subsys)
-{
-    return false;
-}
-
-bool chan_no_enable_x11_forwarding(
-    Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata,
-    unsigned screen_number)
-{
-    return false;
-}
-
-bool chan_no_enable_agent_forwarding(Channel *chan)
-{
-    return false;
-}
-
-bool chan_no_allocate_pty(
-    Channel *chan, ptrlen termtype, unsigned width, unsigned height,
-    unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes)
-{
-    return false;
-}
-
-bool chan_no_set_env(Channel *chan, ptrlen var, ptrlen value)
-{
-    return false;
-}
-
-bool chan_no_send_break(Channel *chan, unsigned length)
-{
-    return false;
-}
-
-bool chan_no_send_signal(Channel *chan, ptrlen signame)
-{
-    return false;
-}
-
-bool chan_no_change_window_size(
-    Channel *chan, unsigned width, unsigned height,
-    unsigned pixwidth, unsigned pixheight)
-{
-    return false;
-}
-
-void chan_no_request_response(Channel *chan, bool success)
-{
-    unreachable("this channel type should never send a want-reply request");
-}
-
 /* ----------------------------------------------------------------------
  * Common routines for handling SSH tty modes.
  */
@@ -961,28 +858,28 @@ bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin)
  * Function to check a host key against any manually configured in Conf.
  */
 
-int verify_ssh_manual_host_key(
-    Conf *conf, const char *fingerprint, ssh_key *key)
+int verify_ssh_manual_host_key(Conf *conf, char **fingerprints, ssh_key *key)
 {
     if (!conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, 0))
         return -1;                     /* no manual keys configured */
 
-    if (fingerprint) {
-        /*
-         * The fingerprint string we've been given will have things
-         * like 'ssh-rsa 2048' at the front of it. Strip those off and
-         * narrow down to just the colon-separated hex block at the
-         * end of the string.
-         */
-        const char *p = strrchr(fingerprint, ' ');
-        fingerprint = p ? p+1 : fingerprint;
-        /* Quick sanity checks, including making sure it's in lowercase */
-        assert(strlen(fingerprint) == 16*3 - 1);
-        assert(fingerprint[2] == ':');
-        assert(fingerprint[strspn(fingerprint, "0123456789abcdef:")] == 0);
-
-        if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, fingerprint))
-            return 1;                  /* success */
+    if (fingerprints) {
+        for (size_t i = 0; i < SSH_N_FPTYPES; i++) {
+            /*
+             * Each fingerprint string we've been given will have
+             * things like 'ssh-rsa 2048' at the front of it. Strip
+             * those off and narrow down to just the hash at the end
+             * of the string.
+             */
+            const char *fingerprint = fingerprints[i];
+            if (!fingerprint)
+                continue;
+            const char *p = strrchr(fingerprint, ' ');
+            fingerprint = p ? p+1 : fingerprint;
+            if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys,
+                                     fingerprint))
+                return 1;                  /* success */
+        }
     }
 
     if (key) {
@@ -1080,17 +977,3 @@ void ssh1_compute_session_id(
     put_data(hash, cookie, 8);
     ssh_hash_final(hash, session_id);
 }
-
-/* ----------------------------------------------------------------------
- * Other miscellaneous utility functions.
- */
-
-void free_rportfwd(struct ssh_rportfwd *rpf)
-{
-    if (rpf) {
-        sfree(rpf->log_description);
-        sfree(rpf->shost);
-        sfree(rpf->dhost);
-        sfree(rpf);
-    }
-}

+ 7 - 5
source/putty/sshcr.h

@@ -25,17 +25,19 @@
  *    Database for Edit and Continue'.
  */
 
-#define crBegin(v)      { int *crLine = &v; switch(v) { case 0:;
+#define crBegin(v)      do { int *crLine = &v; switch(v) { case 0:
 #define crBeginState    crBegin(s->crLine)
 #define crStateP(t, v)                          \
     struct t *s;                                \
     if (!(v)) { s = (v) = snew(struct t); s->crLine = 0; }      \
     s = (v);
 #define crState(t)      crStateP(t, ssh->t)
-#define crFinish(z)     } *crLine = 0; return (z); }
-#define crFinishV       } *crLine = 0; return; }
-#define crFinishFree(z) } sfree(s); return (z); }
-#define crFinishFreeV   } sfree(s); return; }
+#define crFinish(z)     } *crLine = 0; return (z); } while (0)
+#define crFinishV       } *crLine = 0; return; } while (0)
+#define crFinishFreed(z) } return (z); } while (0)
+#define crFinishFreedV   } return; } while (0)
+#define crFinishFree(z) } sfree(s); return (z); } while (0)
+#define crFinishFreeV   } sfree(s); return; } while (0)
 #define crReturn(z)     \
         do {\
             *crLine =__LINE__; return (z); case __LINE__:;\

+ 2 - 8
source/putty/sshcrcda.c

@@ -44,14 +44,8 @@
 
 #define CMP(a, b)       (memcmp(a, b, SSH_BLOCKSIZE))
 
-#ifdef MPEXT
-static
-#endif
-uint8_t ONE[4] = { 1, 0, 0, 0 };
-#ifdef MPEXT
-static
-#endif
-uint8_t ZERO[4] = { 0, 0, 0, 0 };
+static const uint8_t ONE[4] = { 1, 0, 0, 0 };
+static const uint8_t ZERO[4] = { 0, 0, 0, 0 };
 
 struct crcda_ctx {
     uint16_t *h;

+ 60 - 16
source/putty/sshdes.c

@@ -435,7 +435,7 @@ static inline uint64_t bitsel(
     return ret;
 }
 
-void des_key_setup(uint64_t key, des_keysched *sched)
+static void des_key_setup(uint64_t key, des_keysched *sched)
 {
     static const int8_t PC1[] = {
          7, 15, 23, 31, 39, 47, 55, 63,  6, 14, 22, 30, 38, 46,
@@ -691,16 +691,34 @@ static void des_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len)
 }
 
 const ssh_cipheralg ssh_des = {
-    des_cbc_new, des_cbc_free, des_cbc_setiv, des_cbc_setkey,
-    des_cbc_encrypt, des_cbc_decrypt, NULL, NULL, "des-cbc",
-    8, 56, 8, SSH_CIPHER_IS_CBC, "single-DES CBC", NULL
+    .new = des_cbc_new,
+    .free = des_cbc_free,
+    .setiv = des_cbc_setiv,
+    .setkey = des_cbc_setkey,
+    .encrypt = des_cbc_encrypt,
+    .decrypt = des_cbc_decrypt,
+    .ssh2_id = "des-cbc",
+    .blksize = 8,
+    .real_keybits = 56,
+    .padded_keybytes = 8,
+    .flags = SSH_CIPHER_IS_CBC,
+    .text_name = "single-DES CBC",
 };
 
 const ssh_cipheralg ssh_des_sshcom_ssh2 = {
     /* Same as ssh_des_cbc, but with a different SSH-2 ID */
-    des_cbc_new, des_cbc_free, des_cbc_setiv, des_cbc_setkey,
-    des_cbc_encrypt, des_cbc_decrypt, NULL, NULL, "[email protected]",
-    8, 56, 8, SSH_CIPHER_IS_CBC, "single-DES CBC", NULL
+    .new = des_cbc_new,
+    .free = des_cbc_free,
+    .setiv = des_cbc_setiv,
+    .setkey = des_cbc_setkey,
+    .encrypt = des_cbc_encrypt,
+    .decrypt = des_cbc_decrypt,
+    .ssh2_id = "[email protected]",
+    .blksize = 8,
+    .real_keybits = 56,
+    .padded_keybytes = 8,
+    .flags = SSH_CIPHER_IS_CBC,
+    .text_name = "single-DES CBC",
 };
 
 static const ssh_cipheralg *const des_list[] = {
@@ -797,9 +815,18 @@ static void des3_cbc1_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len)
 }
 
 const ssh_cipheralg ssh_3des_ssh2 = {
-    des3_cbc1_new, des3_cbc1_free, des3_cbc1_setiv, des3_cbc1_setkey,
-    des3_cbc1_cbc_encrypt, des3_cbc1_cbc_decrypt, NULL, NULL, "3des-cbc",
-    8, 168, 24, SSH_CIPHER_IS_CBC, "triple-DES CBC", NULL
+    .new = des3_cbc1_new,
+    .free = des3_cbc1_free,
+    .setiv = des3_cbc1_setiv,
+    .setkey = des3_cbc1_setkey,
+    .encrypt = des3_cbc1_cbc_encrypt,
+    .decrypt = des3_cbc1_cbc_decrypt,
+    .ssh2_id = "3des-cbc",
+    .blksize = 8,
+    .real_keybits = 168,
+    .padded_keybytes = 24,
+    .flags = SSH_CIPHER_IS_CBC,
+    .text_name = "triple-DES CBC",
 };
 
 /* ----------------------------------------------------------------------
@@ -894,9 +921,18 @@ static void des3_sdctr_encrypt_decrypt(ssh_cipher *ciph, void *vdata, int len)
 }
 
 const ssh_cipheralg ssh_3des_ssh2_ctr = {
-    des3_sdctr_new, des3_sdctr_free, des3_sdctr_setiv, des3_sdctr_setkey,
-    des3_sdctr_encrypt_decrypt, des3_sdctr_encrypt_decrypt,
-    NULL, NULL, "3des-ctr", 8, 168, 24, 0, "triple-DES SDCTR", NULL
+    .new = des3_sdctr_new,
+    .free = des3_sdctr_free,
+    .setiv = des3_sdctr_setiv,
+    .setkey = des3_sdctr_setkey,
+    .encrypt = des3_sdctr_encrypt_decrypt,
+    .decrypt = des3_sdctr_encrypt_decrypt,
+    .ssh2_id = "3des-ctr",
+    .blksize = 8,
+    .real_keybits = 168,
+    .padded_keybytes = 24,
+    .flags = 0,
+    .text_name = "triple-DES SDCTR",
 };
 
 static const ssh_cipheralg *const des3_list[] = {
@@ -1026,7 +1062,15 @@ static void des3_cbc3_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len)
 }
 
 const ssh_cipheralg ssh_3des_ssh1 = {
-    des3_cbc3_new, des3_cbc3_free, des3_cbc3_setiv, des3_cbc3_setkey,
-    des3_cbc3_cbc_encrypt, des3_cbc3_cbc_decrypt, NULL, NULL, NULL,
-    8, 168, 24, SSH_CIPHER_IS_CBC, "triple-DES inner-CBC", NULL
+    .new = des3_cbc3_new,
+    .free = des3_cbc3_free,
+    .setiv = des3_cbc3_setiv,
+    .setkey = des3_cbc3_setkey,
+    .encrypt = des3_cbc3_cbc_encrypt,
+    .decrypt = des3_cbc3_cbc_decrypt,
+    .blksize = 8,
+    .real_keybits = 168,
+    .padded_keybytes = 24,
+    .flags = SSH_CIPHER_IS_CBC,
+    .text_name = "triple-DES inner-CBC",
 };

+ 34 - 21
source/putty/sshdss.c

@@ -87,6 +87,23 @@ static char *dss_cache_str(ssh_key *key)
     return strbuf_to_str(sb);
 }
 
+static key_components *dss_components(ssh_key *key)
+{
+    struct dss_key *dss = container_of(key, struct dss_key, sshk);
+    key_components *kc = key_components_new();
+
+    key_components_add_text(kc, "key_type", "DSA");
+    assert(dss->p);
+    key_components_add_mp(kc, "p", dss->p);
+    key_components_add_mp(kc, "q", dss->q);
+    key_components_add_mp(kc, "g", dss->g);
+    key_components_add_mp(kc, "public_y", dss->y);
+    if (dss->x)
+        key_components_add_mp(kc, "private_x", dss->x);
+
+    return kc;
+}
+
 static char *dss_invalid(ssh_key *key, unsigned flags)
 {
     /* No validity criterion will stop us from using a DSA key at all */
@@ -411,12 +428,12 @@ mp_int *dss_gen_k(const char *id_string, mp_int *modulus,
     h = ssh_hash_new(&ssh_sha512);
     put_asciz(h, id_string);
     put_mp_ssh2(h, private_key);
-    ssh_hash_final(h, digest512);
+    ssh_hash_digest(h, digest512);
 
     /*
      * Now hash that digest plus the message hash.
      */
-    h = ssh_hash_new(&ssh_sha512);
+    ssh_hash_reset(h);
     put_data(h, digest512, sizeof(digest512));
     put_data(h, digest, digest_len);
     ssh_hash_final(h, digest512);
@@ -488,23 +505,19 @@ static void dss_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs)
 }
 
 const ssh_keyalg ssh_dss = {
-    dss_new_pub,
-    dss_new_priv,
-    dss_new_priv_openssh,
-
-    dss_freekey,
-    dss_invalid,
-    dss_sign,
-    dss_verify,
-    dss_public_blob,
-    dss_private_blob,
-    dss_openssh_blob,
-    dss_cache_str,
-
-    dss_pubkey_bits,
-
-    "ssh-dss",
-    "dss",
-    NULL,
-    0, /* no supported flags */
+    .new_pub = dss_new_pub,
+    .new_priv = dss_new_priv,
+    .new_priv_openssh = dss_new_priv_openssh,
+    .freekey = dss_freekey,
+    .invalid = dss_invalid,
+    .sign = dss_sign,
+    .verify = dss_verify,
+    .public_blob = dss_public_blob,
+    .private_blob = dss_private_blob,
+    .openssh_blob = dss_openssh_blob,
+    .cache_str = dss_cache_str,
+    .components = dss_components,
+    .pubkey_bits = dss_pubkey_bits,
+    .ssh_id = "ssh-dss",
+    .cache_id = "dss",
 };

+ 268 - 125
source/putty/sshecc.c

@@ -7,7 +7,7 @@
  *
  *       Montgomery form curves are supported for DH. (Curve25519)
  *
- *       Edwards form curves are supported for DSA. (Ed25519)
+ *       Edwards form curves are supported for DSA. (Ed25519, Ed448)
  */
 
 /*
@@ -74,19 +74,20 @@ static void finalize_ecurve(struct ec_curve *curve)
  */
 
 static void initialise_common(
-    struct ec_curve *curve, EllipticCurveType type, mp_int *p)
+    struct ec_curve *curve, EllipticCurveType type, mp_int *p,
+    unsigned extrabits)
 {
     curve->type = type;
     curve->p = mp_copy(p);
     curve->fieldBits = mp_get_nbits(p);
-    curve->fieldBytes = (curve->fieldBits + 7) / 8;
+    curve->fieldBytes = (curve->fieldBits + extrabits + 7) / 8;
 }
 
 static void initialise_wcurve(
     struct ec_curve *curve, mp_int *p, mp_int *a, mp_int *b,
     mp_int *nonsquare, mp_int *G_x, mp_int *G_y, mp_int *G_order)
 {
-    initialise_common(curve, EC_WEIERSTRASS, p);
+    initialise_common(curve, EC_WEIERSTRASS, p, 0);
 
     curve->w.wc = ecc_weierstrass_curve(p, a, b, nonsquare);
 
@@ -98,7 +99,7 @@ static void initialise_mcurve(
     struct ec_curve *curve, mp_int *p, mp_int *a, mp_int *b,
     mp_int *G_x, unsigned log2_cofactor)
 {
-    initialise_common(curve, EC_MONTGOMERY, p);
+    initialise_common(curve, EC_MONTGOMERY, p, 0);
 
     curve->m.mc = ecc_montgomery_curve(p, a, b);
     curve->m.log2_cofactor = log2_cofactor;
@@ -108,11 +109,15 @@ static void initialise_mcurve(
 
 static void initialise_ecurve(
     struct ec_curve *curve, mp_int *p, mp_int *d, mp_int *a,
-    mp_int *nonsquare, mp_int *G_x, mp_int *G_y, mp_int *G_order)
+    mp_int *nonsquare, mp_int *G_x, mp_int *G_y, mp_int *G_order,
+    unsigned log2_cofactor)
 {
-    initialise_common(curve, EC_EDWARDS, p);
+    /* Ensure curve->fieldBytes is long enough to store an extra bit
+     * for a compressed point */
+    initialise_common(curve, EC_EDWARDS, p, 1);
 
     curve->e.ec = ecc_edwards_curve(p, d, a, nonsquare);
+    curve->e.log2_cofactor = log2_cofactor;
 
     curve->e.G = ecc_edwards_point_new(curve->e.ec, G_x, G_y);
     curve->e.G_order = mp_copy(G_order);
@@ -279,6 +284,35 @@ static struct ec_curve *ec_curve25519(void)
     return &curve;
 }
 
+static struct ec_curve *ec_curve448(void)
+{
+    static struct ec_curve curve = { 0 };
+    static bool initialised = false;
+
+    if (!initialised)
+    {
+        mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
+        mp_int *a = MP_LITERAL(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000262a6);
+        mp_int *b = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001);
+        mp_int *G_x = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005);
+        initialise_mcurve(&curve, p, a, b, G_x, 2);
+        mp_free(p);
+        mp_free(a);
+        mp_free(b);
+        mp_free(G_x);
+
+        /* This curve doesn't need a name, because it's never used in
+         * any format that embeds the curve name */
+        curve.name = NULL;
+        curve.textname = "Curve448";
+
+        /* Now initialised, no need to do it again */
+        initialised = true;
+    }
+
+    return &curve;
+}
+
 static struct ec_curve *ec_ed25519(void)
 {
     static struct ec_curve curve = { 0 };
@@ -302,7 +336,8 @@ static struct ec_curve *ec_ed25519(void)
         mp_int *G_y = MP_LITERAL(0x6666666666666666666666666666666666666666666666666666666666666658);
         mp_int *G_order = MP_LITERAL(0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed);
         mp_int *nonsquare_mod_p = mp_from_integer(2);
-        initialise_ecurve(&curve, p, d, a, nonsquare_mod_p, G_x, G_y, G_order);
+        initialise_ecurve(&curve, p, d, a, nonsquare_mod_p,
+                          G_x, G_y, G_order, 3);
         mp_free(p);
         mp_free(d);
         mp_free(a);
@@ -324,6 +359,43 @@ static struct ec_curve *ec_ed25519(void)
     return &curve;
 }
 
+static struct ec_curve *ec_ed448(void)
+{
+    static struct ec_curve curve = { 0 };
+    static bool initialised = false;
+
+    if (!initialised)
+    {
+        mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
+        mp_int *d = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffff6756); /* = p - 39081 */
+        mp_int *a = MP_LITERAL(0x1);
+        mp_int *G_x = MP_LITERAL(0x4f1970c66bed0ded221d15a622bf36da9e146570470f1767ea6de324a3d3a46412ae1af72ab66511433b80e18b00938e2626a82bc70cc05e);
+        mp_int *G_y = MP_LITERAL(0x693f46716eb6bc248876203756c9c7624bea73736ca3984087789c1e05a0c2d73ad3ff1ce67c39c4fdbd132c4ed7c8ad9808795bf230fa14);
+        mp_int *G_order = MP_LITERAL(0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3);
+        mp_int *nonsquare_mod_p = mp_from_integer(7);
+        initialise_ecurve(&curve, p, d, a, nonsquare_mod_p,
+                          G_x, G_y, G_order, 2);
+        mp_free(p);
+        mp_free(d);
+        mp_free(a);
+        mp_free(G_x);
+        mp_free(G_y);
+        mp_free(G_order);
+        mp_free(nonsquare_mod_p);
+
+        /* This curve doesn't need a name, because it's never used in
+         * any format that embeds the curve name */
+        curve.name = NULL;
+
+        curve.textname = "Ed448";
+
+        /* Now initialised, no need to do it again */
+        initialised = true;
+    }
+
+    return &curve;
+}
+
 /* ----------------------------------------------------------------------
  * Public point from private
  */
@@ -335,6 +407,10 @@ struct ecsign_extra {
     /* These fields are used by the OpenSSH PEM format importer/exporter */
     const unsigned char *oid;
     int oidlen;
+
+    /* Some EdDSA instances prefix a string to all hash preimages, to
+     * disambiguate which signature variant they're being used with */
+    ptrlen hash_prefix;
 };
 
 WeierstrassPoint *ecdsa_public(mp_int *private_key, const ssh_keyalg *alg)
@@ -368,11 +444,11 @@ static mp_int *eddsa_exponent_from_hash(
     mp_reduce_mod_2to(e, curve->fieldBits);
 
     /*
-     * Clear exactly three low bits.
+     * Clear a curve-specific number of low bits.
      */
-    { // WINSCP
-    size_t bit; // WINSCP
-    for (bit = 0; bit < 3; bit++)
+    // WINSCP
+    unsigned bit; // WINSCP 
+    for (bit = 0; bit < curve->e.log2_cofactor; bit++)
         mp_set_bit(e, bit, 0);
     } // WINSCP
 
@@ -417,10 +493,9 @@ static mp_int *BinarySource_get_mp_le(BinarySource *src)
 }
 #define get_mp_le(src) BinarySource_get_mp_le(BinarySource_UPCAST(src))
 
-static void BinarySink_put_mp_le_unsigned(BinarySink *bs, mp_int *x)
+static void BinarySink_put_mp_le_fixedlen(BinarySink *bs, mp_int *x,
+                                          size_t bytes)
 {
-    size_t bytes = (mp_get_nbits(x) + 7) / 8;
-
     put_uint32(bs, bytes);
     { // WINSCP
     size_t i; // WINSCP
@@ -428,8 +503,8 @@ static void BinarySink_put_mp_le_unsigned(BinarySink *bs, mp_int *x)
         put_byte(bs, mp_get_byte(x, i));
     } // WINSCP
 }
-#define put_mp_le_unsigned(bs, x) \
-    BinarySink_put_mp_le_unsigned(BinarySink_UPCAST(bs), x)
+#define put_mp_le_fixedlen(bs, x, bytes)                        \
+    BinarySink_put_mp_le_fixedlen(BinarySink_UPCAST(bs), x, bytes)
 
 static WeierstrassPoint *ecdsa_decode(
     ptrlen encoded, const struct ec_curve *curve)
@@ -553,22 +628,22 @@ static void BinarySink_put_wpoint(
 static EdwardsPoint *eddsa_decode(ptrlen encoded, const struct ec_curve *curve)
 {
     assert(curve->type == EC_EDWARDS);
-    assert(curve->fieldBits % 8 == 7);
 
     { // WINSCP
     mp_int *y = mp_from_bytes_le(encoded);
 
-    if (mp_get_nbits(y) > curve->fieldBits+1) {
+    /* The topmost bit of the encoding isn't part of y, so it stores
+     * the bottom bit of x. Extract it, and zero that bit in y. */
+    unsigned desired_x_parity = mp_get_bit(y, curve->fieldBytes * 8 - 1);
+    mp_set_bit(y, curve->fieldBytes * 8 - 1, 0);
+
+    /* What's left should now be within the range of the curve's modulus */
+    if (mp_cmp_hs(y, curve->p)) {
         mp_free(y);
         return NULL;
     }
 
-    /* The topmost bit of the encoding isn't part of y, so it stores
-     * the bottom bit of x. Extract it, and zero that bit in y. */
     { // WINSCP
-    unsigned desired_x_parity = mp_get_bit(y, curve->fieldBits);
-    mp_set_bit(y, curve->fieldBits, 0);
-
     { // WINSCP
     EdwardsPoint *P = ecc_edwards_point_new_from_y(
         curve->e.ec, y, desired_x_parity);
@@ -748,6 +823,27 @@ static char *ecdsa_cache_str(ssh_key *key)
     } // WINSCP
 }
 
+static key_components *ecdsa_components(ssh_key *key)
+{
+    struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
+    key_components *kc = key_components_new();
+
+    key_components_add_text(kc, "key_type", "ECDSA");
+    key_components_add_text(kc, "curve_name", ek->curve->textname);
+
+    mp_int *x, *y;
+    ecc_weierstrass_get_affine(ek->publicKey, &x, &y);
+    key_components_add_mp(kc, "public_affine_x", x);
+    key_components_add_mp(kc, "public_affine_y", y);
+    mp_free(x);
+    mp_free(y);
+
+    if (ek->privateKey)
+        key_components_add_mp(kc, "private_exponent", ek->privateKey);
+
+    return kc;
+}
+
 static char *eddsa_cache_str(ssh_key *key)
 {
     struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
@@ -762,6 +858,27 @@ static char *eddsa_cache_str(ssh_key *key)
     } // WINSCP
 }
 
+static key_components *eddsa_components(ssh_key *key)
+{
+    struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
+    key_components *kc = key_components_new();
+
+    key_components_add_text(kc, "key_type", "EdDSA");
+    key_components_add_text(kc, "curve_name", ek->curve->textname);
+
+    mp_int *x, *y;
+    ecc_edwards_get_affine(ek->publicKey, &x, &y);
+    key_components_add_mp(kc, "public_affine_x", x);
+    key_components_add_mp(kc, "public_affine_y", y);
+    mp_free(x);
+    mp_free(y);
+
+    if (ek->privateKey)
+        key_components_add_mp(kc, "private_exponent", ek->privateKey);
+
+    return kc;
+}
+
 static void ecdsa_public_blob(ssh_key *key, BinarySink *bs)
 {
     struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
@@ -794,7 +911,7 @@ static void eddsa_private_blob(ssh_key *key, BinarySink *bs)
 
     /* EdDSA stores the private key integer little-endian and unsigned */
     assert(ek->privateKey);
-    put_mp_le_unsigned(bs, ek->privateKey);
+    put_mp_le_fixedlen(bs, ek->privateKey, ek->curve->fieldBytes);
 }
 
 static ssh_key *ecdsa_new_priv(const ssh_keyalg *alg, ptrlen pub, ptrlen priv)
@@ -850,6 +967,10 @@ static ssh_key *eddsa_new_priv_openssh(
      * correct as well, otherwise the key we think we've imported
      * won't behave identically to the way OpenSSH would have treated
      * it.
+     *
+     * We assume that Ed448 will work the same way, as and when
+     * OpenSSH implements it, which at the time of writing this they
+     * had not.
      */
     { // WINSCP
     BinarySource subsrc[1];
@@ -896,7 +1017,7 @@ static void eddsa_openssh_blob(ssh_key *key, BinarySink *bs)
     ptrlen pub = make_ptrlen(pub_sb->s + 4, pub_sb->len - 4);
 
     strbuf *priv_sb = strbuf_new_nm();
-    put_mp_le_unsigned(priv_sb, ek->privateKey);
+    put_mp_le_fixedlen(priv_sb, ek->privateKey, ek->curve->fieldBytes);
     { // WINSCP
     ptrlen priv = make_ptrlen(priv_sb->s + 4, priv_sb->len - 4);
 
@@ -1081,6 +1202,7 @@ static mp_int *eddsa_signing_exponent_from_data(
     /* Hash (r || public key || message) */
     unsigned char hash[MAX_HASH_LEN];
     ssh_hash *h = ssh_hash_new(extra->hash);
+    put_datapl(h, extra->hash_prefix);
     put_datapl(h, r_encoded);
     put_epoint(h, ek->publicKey, ek->curve, true); /* omit string header */
     put_datapl(h, data);
@@ -1266,6 +1388,7 @@ static void eddsa_sign(ssh_key *key, ptrlen data,
      * generate the signature point r.
      */
     h = ssh_hash_new(extra->hash);
+    put_datapl(h, extra->hash_prefix);
     put_data(h, hash + ek->curve->fieldBytes,
              extra->hash->hlen - ek->curve->fieldBytes);
     put_datapl(h, data);
@@ -1320,120 +1443,131 @@ static void eddsa_sign(ssh_key *key, ptrlen data,
     } // WINSCP
 }
 
-const struct ecsign_extra sign_extra_ed25519 = {
+static const struct ecsign_extra sign_extra_ed25519 = {
     ec_ed25519, &ssh_sha512,
-    NULL, 0,
+    NULL, 0, PTRLEN_DECL_LITERAL(""),
 };
 const ssh_keyalg ssh_ecdsa_ed25519 = {
-    eddsa_new_pub,
-    eddsa_new_priv,
-    eddsa_new_priv_openssh,
-
-    eddsa_freekey,
-    ec_signkey_invalid,
-    eddsa_sign,
-    eddsa_verify,
-    eddsa_public_blob,
-    eddsa_private_blob,
-    eddsa_openssh_blob,
-    eddsa_cache_str,
-
-    ec_shared_pubkey_bits,
-
-    "ssh-ed25519",
-    "ssh-ed25519",
-    &sign_extra_ed25519,
-    0, /* no supported flags */
+    .new_pub = eddsa_new_pub,
+    .new_priv = eddsa_new_priv,
+    .new_priv_openssh = eddsa_new_priv_openssh,
+    .freekey = eddsa_freekey,
+    .invalid = ec_signkey_invalid,
+    .sign = eddsa_sign,
+    .verify = eddsa_verify,
+    .public_blob = eddsa_public_blob,
+    .private_blob = eddsa_private_blob,
+    .openssh_blob = eddsa_openssh_blob,
+    .cache_str = eddsa_cache_str,
+    .components = eddsa_components,
+    .pubkey_bits = ec_shared_pubkey_bits,
+    .ssh_id = "ssh-ed25519",
+    .cache_id = "ssh-ed25519",
+    .extra = &sign_extra_ed25519,
+};
+
+static const struct ecsign_extra sign_extra_ed448 = {
+    ec_ed448, &ssh_shake256_114bytes,
+    NULL, 0, PTRLEN_DECL_LITERAL("SigEd448\0\0"),
+};
+const ssh_keyalg ssh_ecdsa_ed448 = {
+    .new_pub = eddsa_new_pub,
+    .new_priv = eddsa_new_priv,
+    .new_priv_openssh = eddsa_new_priv_openssh,
+    .freekey = eddsa_freekey,
+    .invalid = ec_signkey_invalid,
+    .sign = eddsa_sign,
+    .verify = eddsa_verify,
+    .public_blob = eddsa_public_blob,
+    .private_blob = eddsa_private_blob,
+    .openssh_blob = eddsa_openssh_blob,
+    .cache_str = eddsa_cache_str,
+    .components = eddsa_components,
+    .pubkey_bits = ec_shared_pubkey_bits,
+    .ssh_id = "ssh-ed448",
+    .cache_id = "ssh-ed448",
+    .extra = &sign_extra_ed448,
 };
 
 /* OID: 1.2.840.10045.3.1.7 (ansiX9p256r1) */
 static const unsigned char nistp256_oid[] = {
     0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07
 };
-const struct ecsign_extra sign_extra_nistp256 = {
+static const struct ecsign_extra sign_extra_nistp256 = {
     ec_p256, &ssh_sha256,
     nistp256_oid, lenof(nistp256_oid),
 };
 const ssh_keyalg ssh_ecdsa_nistp256 = {
-    ecdsa_new_pub,
-    ecdsa_new_priv,
-    ecdsa_new_priv_openssh,
-
-    ecdsa_freekey,
-    ec_signkey_invalid,
-    ecdsa_sign,
-    ecdsa_verify,
-    ecdsa_public_blob,
-    ecdsa_private_blob,
-    ecdsa_openssh_blob,
-    ecdsa_cache_str,
-
-    ec_shared_pubkey_bits,
-
-    "ecdsa-sha2-nistp256",
-    "ecdsa-sha2-nistp256",
-    &sign_extra_nistp256,
-    0, /* no supported flags */
+    .new_pub = ecdsa_new_pub,
+    .new_priv = ecdsa_new_priv,
+    .new_priv_openssh = ecdsa_new_priv_openssh,
+    .freekey = ecdsa_freekey,
+    .invalid = ec_signkey_invalid,
+    .sign = ecdsa_sign,
+    .verify = ecdsa_verify,
+    .public_blob = ecdsa_public_blob,
+    .private_blob = ecdsa_private_blob,
+    .openssh_blob = ecdsa_openssh_blob,
+    .cache_str = ecdsa_cache_str,
+    .components = ecdsa_components,
+    .pubkey_bits = ec_shared_pubkey_bits,
+    .ssh_id = "ecdsa-sha2-nistp256",
+    .cache_id = "ecdsa-sha2-nistp256",
+    .extra = &sign_extra_nistp256,
 };
 
 /* OID: 1.3.132.0.34 (secp384r1) */
 static const unsigned char nistp384_oid[] = {
     0x2b, 0x81, 0x04, 0x00, 0x22
 };
-const struct ecsign_extra sign_extra_nistp384 = {
+static const struct ecsign_extra sign_extra_nistp384 = {
     ec_p384, &ssh_sha384,
     nistp384_oid, lenof(nistp384_oid),
 };
 const ssh_keyalg ssh_ecdsa_nistp384 = {
-    ecdsa_new_pub,
-    ecdsa_new_priv,
-    ecdsa_new_priv_openssh,
-
-    ecdsa_freekey,
-    ec_signkey_invalid,
-    ecdsa_sign,
-    ecdsa_verify,
-    ecdsa_public_blob,
-    ecdsa_private_blob,
-    ecdsa_openssh_blob,
-    ecdsa_cache_str,
-
-    ec_shared_pubkey_bits,
-
-    "ecdsa-sha2-nistp384",
-    "ecdsa-sha2-nistp384",
-    &sign_extra_nistp384,
-    0, /* no supported flags */
+    .new_pub = ecdsa_new_pub,
+    .new_priv = ecdsa_new_priv,
+    .new_priv_openssh = ecdsa_new_priv_openssh,
+    .freekey = ecdsa_freekey,
+    .invalid = ec_signkey_invalid,
+    .sign = ecdsa_sign,
+    .verify = ecdsa_verify,
+    .public_blob = ecdsa_public_blob,
+    .private_blob = ecdsa_private_blob,
+    .openssh_blob = ecdsa_openssh_blob,
+    .cache_str = ecdsa_cache_str,
+    .components = ecdsa_components,
+    .pubkey_bits = ec_shared_pubkey_bits,
+    .ssh_id = "ecdsa-sha2-nistp384",
+    .cache_id = "ecdsa-sha2-nistp384",
+    .extra = &sign_extra_nistp384,
 };
 
 /* OID: 1.3.132.0.35 (secp521r1) */
 static const unsigned char nistp521_oid[] = {
     0x2b, 0x81, 0x04, 0x00, 0x23
 };
-const struct ecsign_extra sign_extra_nistp521 = {
+static const struct ecsign_extra sign_extra_nistp521 = {
     ec_p521, &ssh_sha512,
     nistp521_oid, lenof(nistp521_oid),
 };
 const ssh_keyalg ssh_ecdsa_nistp521 = {
-    ecdsa_new_pub,
-    ecdsa_new_priv,
-    ecdsa_new_priv_openssh,
-
-    ecdsa_freekey,
-    ec_signkey_invalid,
-    ecdsa_sign,
-    ecdsa_verify,
-    ecdsa_public_blob,
-    ecdsa_private_blob,
-    ecdsa_openssh_blob,
-    ecdsa_cache_str,
-
-    ec_shared_pubkey_bits,
-
-    "ecdsa-sha2-nistp521",
-    "ecdsa-sha2-nistp521",
-    &sign_extra_nistp521,
-    0, /* no supported flags */
+    .new_pub = ecdsa_new_pub,
+    .new_priv = ecdsa_new_priv,
+    .new_priv_openssh = ecdsa_new_priv_openssh,
+    .freekey = ecdsa_freekey,
+    .invalid = ec_signkey_invalid,
+    .sign = ecdsa_sign,
+    .verify = ecdsa_verify,
+    .public_blob = ecdsa_public_blob,
+    .private_blob = ecdsa_private_blob,
+    .openssh_blob = ecdsa_openssh_blob,
+    .cache_str = ecdsa_cache_str,
+    .components = ecdsa_components,
+    .pubkey_bits = ec_shared_pubkey_bits,
+    .ssh_id = "ecdsa-sha2-nistp521",
+    .cache_id = "ecdsa-sha2-nistp521",
+    .extra = &sign_extra_nistp521,
 };
 
 /* ----------------------------------------------------------------------
@@ -1567,21 +1701,6 @@ static mp_int *ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey)
      * will be reduced mod p. */
     mp_reduce_mod_2to(remote_x, dh->curve->fieldBits);
 
-    if (mp_eq_integer(remote_x, 0)) {
-        /*
-         * The libssh spec for Curve25519 key exchange says that
-         * 'every possible public key maps to a valid ECC Point' and
-         * therefore no validation needs to be done on the server's
-         * provided x-coordinate. However, I don't believe it: an
-         * x-coordinate of zero doesn't work sensibly, because you end
-         * up dividing by zero in the doubling formula
-         * (x+1)^2(x-1)^2/(4(x^3+ax^2+x)). (Put another way, although
-         * that point P is not the _identity_ of the curve, it is a
-         * torsion point such that 2P is the identity.)
-         */
-        mp_free(remote_x);
-        return NULL;
-    }
     { // WINSCP
     MontgomeryPoint *remote_p = ecc_montgomery_point_new(
         dh->curve->m.mc, remote_x);
@@ -1589,6 +1708,13 @@ static mp_int *ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey)
 
     { // WINSCP
     MontgomeryPoint *p = ecc_montgomery_multiply(remote_p, dh->private);
+
+    if (ecc_montgomery_is_identity(p)) {
+        ecc_montgomery_point_free(remote_p);
+        ecc_montgomery_point_free(p);
+        return NULL;
+    }
+
     mp_int *x;
     ecc_montgomery_get_affine(p, &x);
 
@@ -1663,7 +1789,19 @@ const ssh_kex ssh_ec_kex_curve25519_libssh = {
     &ssh_sha256, &kex_extra_curve25519,
 };
 
-const struct eckex_extra kex_extra_nistp256 = {
+static const struct eckex_extra kex_extra_curve448 = {
+    ec_curve448,
+    ssh_ecdhkex_m_setup,
+    ssh_ecdhkex_m_cleanup,
+    ssh_ecdhkex_m_getpublic,
+    ssh_ecdhkex_m_getkey,
+};
+const ssh_kex ssh_ec_kex_curve448 = {
+    "curve448-sha512", NULL, KEXTYPE_ECDH,
+    &ssh_sha512, &kex_extra_curve448,
+};
+
+static const struct eckex_extra kex_extra_nistp256 = {
     ec_p256,
     ssh_ecdhkex_w_setup,
     ssh_ecdhkex_w_cleanup,
@@ -1675,7 +1813,7 @@ const ssh_kex ssh_ec_kex_nistp256 = {
     &ssh_sha256, &kex_extra_nistp256,
 };
 
-const struct eckex_extra kex_extra_nistp384 = {
+static const struct eckex_extra kex_extra_nistp384 = {
     ec_p384,
     ssh_ecdhkex_w_setup,
     ssh_ecdhkex_w_cleanup,
@@ -1687,7 +1825,7 @@ const ssh_kex ssh_ec_kex_nistp384 = {
     &ssh_sha384, &kex_extra_nistp384,
 };
 
-const struct eckex_extra kex_extra_nistp521 = {
+static const struct eckex_extra kex_extra_nistp521 = {
     ec_p521,
     ssh_ecdhkex_w_setup,
     ssh_ecdhkex_w_cleanup,
@@ -1700,6 +1838,7 @@ const ssh_kex ssh_ec_kex_nistp521 = {
 };
 
 static const ssh_kex *const ec_kex_list[] = {
+    &ssh_ec_kex_curve448,
     &ssh_ec_kex_curve25519,
     &ssh_ec_kex_curve25519_libssh,
     &ssh_ec_kex_nistp256,
@@ -1747,6 +1886,9 @@ const unsigned char *ec_alg_oid(const ssh_keyalg *alg,
 const int ec_nist_curve_lengths[] = { 256, 384, 521 };
 const int n_ec_nist_curve_lengths = lenof(ec_nist_curve_lengths);
 
+const int ec_ed_curve_lengths[] = { 255, 448 };
+const int n_ec_ed_curve_lengths = lenof(ec_ed_curve_lengths);
+
 bool ec_nist_alg_and_curve_by_bits(
     int bits, const struct ec_curve **curve, const ssh_keyalg **alg)
 {
@@ -1765,6 +1907,7 @@ bool ec_ed_alg_and_curve_by_bits(
 {
     switch (bits) {
       case 255: case 256: *alg = &ssh_ecdsa_ed25519; break;
+      case 448: *alg = &ssh_ecdsa_ed448; break;
       default: return false;
     }
     *curve = ((struct ecsign_extra *)(*alg)->extra)->curve();

+ 78 - 65
source/putty/sshhmac.c

@@ -8,7 +8,6 @@
 struct hmac {
     const ssh_hashalg *hashalg;
     ssh_hash *h_outer, *h_inner, *h_live;
-    bool keyed;
     uint8_t *digest;
     strbuf *text_name;
     ssh2_mac mac;
@@ -30,7 +29,6 @@ static ssh2_mac *hmac_new(const ssh2_macalg *alg, ssh_cipher *cipher)
     ctx->hashalg = ssh_hash_alg(ctx->h_outer);
     ctx->h_inner = ssh_hash_new(ctx->hashalg);
     ctx->h_live = ssh_hash_new(ctx->hashalg);
-    ctx->keyed = false;
 
     /*
      * HMAC is not well defined as a wrapper on an absolutely general
@@ -94,18 +92,6 @@ static void hmac_key(ssh2_mac *mac, ptrlen key)
     size_t klen;
     strbuf *sb = NULL;
 
-    if (ctx->keyed) {
-        /*
-         * If we've already been keyed, throw away the existing hash
-         * objects and make a fresh pair to put the new key in.
-         */
-        ssh_hash_free(ctx->h_outer);
-        ssh_hash_free(ctx->h_inner);
-        ctx->h_outer = ssh_hash_new(ctx->hashalg);
-        ctx->h_inner = ssh_hash_new(ctx->hashalg);
-    }
-    ctx->keyed = true;
-
     if (key.len > ctx->hashalg->blocklen) {
         /*
          * RFC 2104 section 2: if the key exceeds the block length of
@@ -114,12 +100,8 @@ static void hmac_key(ssh2_mac *mac, ptrlen key)
          */
         sb = strbuf_new_nm();
         strbuf_append(sb, ctx->hashalg->hlen);
-
+        hash_simple(ctx->hashalg, key, sb->u);
         { // WINSCP
-        ssh_hash *htmp = ssh_hash_new(ctx->hashalg);
-        put_datapl(htmp, key);
-        ssh_hash_final(htmp, sb->u);
-
         kp = sb->u;
         klen = sb->len;
         } // WINSCP
@@ -131,12 +113,7 @@ static void hmac_key(ssh2_mac *mac, ptrlen key)
         klen = key.len;
     }
 
-    if (ctx->h_outer)
-        ssh_hash_free(ctx->h_outer);
-    if (ctx->h_inner)
-        ssh_hash_free(ctx->h_inner);
-
-    ctx->h_outer = ssh_hash_new(ctx->hashalg);
+    ssh_hash_reset(ctx->h_outer);
     { // WINSCP
     size_t i; // WINSCP
     for (i = 0; i < klen; i++)
@@ -144,7 +121,7 @@ static void hmac_key(ssh2_mac *mac, ptrlen key)
     for (i = klen; i < ctx->hashalg->blocklen; i++)
         put_byte(ctx->h_outer, PAD_OUTER);
 
-    ctx->h_inner = ssh_hash_new(ctx->hashalg);
+    ssh_hash_reset(ctx->h_inner);
     for (i = 0; i < klen; i++)
         put_byte(ctx->h_inner, PAD_INNER ^ kp[i]);
     for (i = klen; i < ctx->hashalg->blocklen; i++)
@@ -158,10 +135,7 @@ static void hmac_key(ssh2_mac *mac, ptrlen key)
 static void hmac_start(ssh2_mac *mac)
 {
     struct hmac *ctx = container_of(mac, struct hmac, mac);
-
-    ssh_hash_free(ctx->h_live);
-    ctx->h_live = ssh_hash_copy(ctx->h_inner);
-    BinarySink_DELEGATE_INIT(&ctx->mac, ctx->h_live);
+    ssh_hash_copyfrom(ctx->h_live, ctx->h_inner);
 }
 
 static void hmac_genresult(ssh2_mac *mac, unsigned char *output)
@@ -169,11 +143,10 @@ static void hmac_genresult(ssh2_mac *mac, unsigned char *output)
     struct hmac *ctx = container_of(mac, struct hmac, mac);
     ssh_hash *htmp;
 
-    /* Leave h_live in place, so that the SSH-2 BPP can continue
-     * regenerating test results from different-length prefixes of the
-     * packet */
-    htmp = ssh_hash_copy(ctx->h_live);
-    ssh_hash_final(htmp, ctx->digest);
+    /* Leave h_live and h_outer in place, so that the SSH-2 BPP can
+     * continue regenerating test results from different-length
+     * prefixes of the packet */
+    ssh_hash_digest_nondestructive(ctx->h_live, ctx->digest);
 
     htmp = ssh_hash_copy(ctx->h_outer);
     put_data(htmp, ctx->digest, ctx->hashalg->hlen);
@@ -194,58 +167,98 @@ static const char *hmac_text_name(ssh2_mac *mac)
     return ctx->text_name->s;
 }
 
-const struct hmac_extra ssh_hmac_sha256_extra = { &ssh_sha256, "" };
+static const struct hmac_extra ssh_hmac_sha256_extra = { &ssh_sha256, "" };
 const ssh2_macalg ssh_hmac_sha256 = {
-    hmac_new, hmac_free, hmac_key,
-    hmac_start, hmac_genresult, hmac_text_name,
-    "hmac-sha2-256", "[email protected]",
-    32, 32, &ssh_hmac_sha256_extra,
+    .new = hmac_new,
+    .free = hmac_free,
+    .setkey = hmac_key,
+    .start = hmac_start,
+    .genresult = hmac_genresult,
+    .text_name = hmac_text_name,
+    .name = "hmac-sha2-256",
+    .etm_name = "[email protected]",
+    .len = 32,
+    .keylen = 32,
+    .extra = &ssh_hmac_sha256_extra,
 };
 
-const struct hmac_extra ssh_hmac_md5_extra = { &ssh_md5, "" };
+static const struct hmac_extra ssh_hmac_md5_extra = { &ssh_md5, "" };
 const ssh2_macalg ssh_hmac_md5 = {
-    hmac_new, hmac_free, hmac_key,
-    hmac_start, hmac_genresult, hmac_text_name,
-    "hmac-md5", "[email protected]",
-    16, 16, &ssh_hmac_md5_extra,
+    .new = hmac_new,
+    .free = hmac_free,
+    .setkey = hmac_key,
+    .start = hmac_start,
+    .genresult = hmac_genresult,
+    .text_name = hmac_text_name,
+    .name = "hmac-md5",
+    .etm_name = "[email protected]",
+    .len = 16,
+    .keylen = 16,
+    .extra = &ssh_hmac_md5_extra,
 };
 
-const struct hmac_extra ssh_hmac_sha1_extra = { &ssh_sha1, "" };
+static const struct hmac_extra ssh_hmac_sha1_extra = { &ssh_sha1, "" };
 
 const ssh2_macalg ssh_hmac_sha1 = {
-    hmac_new, hmac_free, hmac_key,
-    hmac_start, hmac_genresult, hmac_text_name,
-    "hmac-sha1", "[email protected]",
-    20, 20, &ssh_hmac_sha1_extra,
+    .new = hmac_new,
+    .free = hmac_free,
+    .setkey = hmac_key,
+    .start = hmac_start,
+    .genresult = hmac_genresult,
+    .text_name = hmac_text_name,
+    .name = "hmac-sha1",
+    .etm_name = "[email protected]",
+    .len = 20,
+    .keylen = 20,
+    .extra = &ssh_hmac_sha1_extra,
 };
 
-const struct hmac_extra ssh_hmac_sha1_96_extra = { &ssh_sha1, "-96" };
+static const struct hmac_extra ssh_hmac_sha1_96_extra = { &ssh_sha1, "-96" };
 
 const ssh2_macalg ssh_hmac_sha1_96 = {
-    hmac_new, hmac_free, hmac_key,
-    hmac_start, hmac_genresult, hmac_text_name,
-    "hmac-sha1-96", "[email protected]",
-    12, 20, &ssh_hmac_sha1_96_extra,
+    .new = hmac_new,
+    .free = hmac_free,
+    .setkey = hmac_key,
+    .start = hmac_start,
+    .genresult = hmac_genresult,
+    .text_name = hmac_text_name,
+    .name = "hmac-sha1-96",
+    .etm_name = "[email protected]",
+    .len = 12,
+    .keylen = 20,
+    .extra = &ssh_hmac_sha1_96_extra,
 };
 
-const struct hmac_extra ssh_hmac_sha1_buggy_extra = {
+static const struct hmac_extra ssh_hmac_sha1_buggy_extra = {
     &ssh_sha1, "", "bug-compatible"
 };
 
 const ssh2_macalg ssh_hmac_sha1_buggy = {
-    hmac_new, hmac_free, hmac_key,
-    hmac_start, hmac_genresult, hmac_text_name,
-    "hmac-sha1", NULL,
-    20, 16, &ssh_hmac_sha1_buggy_extra,
+    .new = hmac_new,
+    .free = hmac_free,
+    .setkey = hmac_key,
+    .start = hmac_start,
+    .genresult = hmac_genresult,
+    .text_name = hmac_text_name,
+    .name = "hmac-sha1",
+    .len = 20,
+    .keylen = 16,
+    .extra = &ssh_hmac_sha1_buggy_extra,
 };
 
-const struct hmac_extra ssh_hmac_sha1_96_buggy_extra = {
+static const struct hmac_extra ssh_hmac_sha1_96_buggy_extra = {
     &ssh_sha1, "-96", "bug-compatible"
 };
 
 const ssh2_macalg ssh_hmac_sha1_96_buggy = {
-    hmac_new, hmac_free, hmac_key,
-    hmac_start, hmac_genresult, hmac_text_name,
-    "hmac-sha1-96", NULL,
-    12, 16, &ssh_hmac_sha1_96_buggy_extra,
+    .new = hmac_new,
+    .free = hmac_free,
+    .setkey = hmac_key,
+    .start = hmac_start,
+    .genresult = hmac_genresult,
+    .text_name = hmac_text_name,
+    .name = "hmac-sha1-96",
+    .len = 12,
+    .keylen = 16,
+    .extra = &ssh_hmac_sha1_96_buggy_extra,
 };

+ 190 - 223
source/putty/sshmd5.c

@@ -1,278 +1,245 @@
-#include <assert.h>
-#include "ssh.h"
-
 /*
  * MD5 implementation for PuTTY. Written directly from the spec by
  * Simon Tatham.
  */
 
-typedef struct {
-    uint32_t h[4];
-} MD5_Core_State;
+#include <assert.h>
+#include "ssh.h"
 
-struct MD5Context {
-    MD5_Core_State core;
-    unsigned char block[64];
-    int blkused;
-    uint64_t len;
-    BinarySink_IMPLEMENTATION;
+static const uint32_t md5_initial_state[] = {
+    0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476,
 };
 
-/* ----------------------------------------------------------------------
- * Core MD5 algorithm: processes 16-word blocks into a message digest.
- */
-
-#define F(x,y,z) ( ((x) & (y)) | ((~(x)) & (z)) )
-#define G(x,y,z) ( ((x) & (z)) | ((~(z)) & (y)) )
-#define H(x,y,z) ( (x) ^ (y) ^ (z) )
-#define I(x,y,z) ( (y) ^ ( (x) | ~(z) ) )
-
-#define rol(x,y) ( ((x) << (y)) | (((uint32_t)x) >> (32-y)) )
+static const struct md5_round_constant {
+    uint32_t addition, rotation, msg_index;
+} md5_round_constants[] = {
+    { 0xd76aa478,  7,  0 }, { 0xe8c7b756, 12,  1 },
+    { 0x242070db, 17,  2 }, { 0xc1bdceee, 22,  3 },
+    { 0xf57c0faf,  7,  4 }, { 0x4787c62a, 12,  5 },
+    { 0xa8304613, 17,  6 }, { 0xfd469501, 22,  7 },
+    { 0x698098d8,  7,  8 }, { 0x8b44f7af, 12,  9 },
+    { 0xffff5bb1, 17, 10 }, { 0x895cd7be, 22, 11 },
+    { 0x6b901122,  7, 12 }, { 0xfd987193, 12, 13 },
+    { 0xa679438e, 17, 14 }, { 0x49b40821, 22, 15 },
+    { 0xf61e2562,  5,  1 }, { 0xc040b340,  9,  6 },
+    { 0x265e5a51, 14, 11 }, { 0xe9b6c7aa, 20,  0 },
+    { 0xd62f105d,  5,  5 }, { 0x02441453,  9, 10 },
+    { 0xd8a1e681, 14, 15 }, { 0xe7d3fbc8, 20,  4 },
+    { 0x21e1cde6,  5,  9 }, { 0xc33707d6,  9, 14 },
+    { 0xf4d50d87, 14,  3 }, { 0x455a14ed, 20,  8 },
+    { 0xa9e3e905,  5, 13 }, { 0xfcefa3f8,  9,  2 },
+    { 0x676f02d9, 14,  7 }, { 0x8d2a4c8a, 20, 12 },
+    { 0xfffa3942,  4,  5 }, { 0x8771f681, 11,  8 },
+    { 0x6d9d6122, 16, 11 }, { 0xfde5380c, 23, 14 },
+    { 0xa4beea44,  4,  1 }, { 0x4bdecfa9, 11,  4 },
+    { 0xf6bb4b60, 16,  7 }, { 0xbebfbc70, 23, 10 },
+    { 0x289b7ec6,  4, 13 }, { 0xeaa127fa, 11,  0 },
+    { 0xd4ef3085, 16,  3 }, { 0x04881d05, 23,  6 },
+    { 0xd9d4d039,  4,  9 }, { 0xe6db99e5, 11, 12 },
+    { 0x1fa27cf8, 16, 15 }, { 0xc4ac5665, 23,  2 },
+    { 0xf4292244,  6,  0 }, { 0x432aff97, 10,  7 },
+    { 0xab9423a7, 15, 14 }, { 0xfc93a039, 21,  5 },
+    { 0x655b59c3,  6, 12 }, { 0x8f0ccc92, 10,  3 },
+    { 0xffeff47d, 15, 10 }, { 0x85845dd1, 21,  1 },
+    { 0x6fa87e4f,  6,  8 }, { 0xfe2ce6e0, 10, 15 },
+    { 0xa3014314, 15,  6 }, { 0x4e0811a1, 21, 13 },
+    { 0xf7537e82,  6,  4 }, { 0xbd3af235, 10, 11 },
+    { 0x2ad7d2bb, 15,  2 }, { 0xeb86d391, 21,  9 },
+};
 
-#define subround(f,w,x,y,z,k,s,ti) \
-       w = x + rol(w + f(x,y,z) + block[k] + ti, s)
+typedef struct md5_block md5_block;
+struct md5_block {
+    uint8_t block[64];
+    size_t used;
+    uint64_t len;
+};
 
-static void MD5_Core_Init(MD5_Core_State * s)
+static inline void md5_block_setup(md5_block *blk)
 {
-    s->h[0] = 0x67452301;
-    s->h[1] = 0xefcdab89;
-    s->h[2] = 0x98badcfe;
-    s->h[3] = 0x10325476;
+    blk->used = 0;
+    blk->len = 0;
 }
 
-static void MD5_Block(MD5_Core_State *s, uint32_t *block)
+static inline bool md5_block_write(
+    md5_block *blk, const void **vdata, size_t *len)
 {
-    uint32_t a, b, c, d;
-
-    a = s->h[0];
-    b = s->h[1];
-    c = s->h[2];
-    d = s->h[3];
-
-    subround(F, a, b, c, d, 0, 7, 0xd76aa478);
-    subround(F, d, a, b, c, 1, 12, 0xe8c7b756);
-    subround(F, c, d, a, b, 2, 17, 0x242070db);
-    subround(F, b, c, d, a, 3, 22, 0xc1bdceee);
-    subround(F, a, b, c, d, 4, 7, 0xf57c0faf);
-    subround(F, d, a, b, c, 5, 12, 0x4787c62a);
-    subround(F, c, d, a, b, 6, 17, 0xa8304613);
-    subround(F, b, c, d, a, 7, 22, 0xfd469501);
-    subround(F, a, b, c, d, 8, 7, 0x698098d8);
-    subround(F, d, a, b, c, 9, 12, 0x8b44f7af);
-    subround(F, c, d, a, b, 10, 17, 0xffff5bb1);
-    subround(F, b, c, d, a, 11, 22, 0x895cd7be);
-    subround(F, a, b, c, d, 12, 7, 0x6b901122);
-    subround(F, d, a, b, c, 13, 12, 0xfd987193);
-    subround(F, c, d, a, b, 14, 17, 0xa679438e);
-    subround(F, b, c, d, a, 15, 22, 0x49b40821);
-    subround(G, a, b, c, d, 1, 5, 0xf61e2562);
-    subround(G, d, a, b, c, 6, 9, 0xc040b340);
-    subround(G, c, d, a, b, 11, 14, 0x265e5a51);
-    subround(G, b, c, d, a, 0, 20, 0xe9b6c7aa);
-    subround(G, a, b, c, d, 5, 5, 0xd62f105d);
-    subround(G, d, a, b, c, 10, 9, 0x02441453);
-    subround(G, c, d, a, b, 15, 14, 0xd8a1e681);
-    subround(G, b, c, d, a, 4, 20, 0xe7d3fbc8);
-    subround(G, a, b, c, d, 9, 5, 0x21e1cde6);
-    subround(G, d, a, b, c, 14, 9, 0xc33707d6);
-    subround(G, c, d, a, b, 3, 14, 0xf4d50d87);
-    subround(G, b, c, d, a, 8, 20, 0x455a14ed);
-    subround(G, a, b, c, d, 13, 5, 0xa9e3e905);
-    subround(G, d, a, b, c, 2, 9, 0xfcefa3f8);
-    subround(G, c, d, a, b, 7, 14, 0x676f02d9);
-    subround(G, b, c, d, a, 12, 20, 0x8d2a4c8a);
-    subround(H, a, b, c, d, 5, 4, 0xfffa3942);
-    subround(H, d, a, b, c, 8, 11, 0x8771f681);
-    subround(H, c, d, a, b, 11, 16, 0x6d9d6122);
-    subround(H, b, c, d, a, 14, 23, 0xfde5380c);
-    subround(H, a, b, c, d, 1, 4, 0xa4beea44);
-    subround(H, d, a, b, c, 4, 11, 0x4bdecfa9);
-    subround(H, c, d, a, b, 7, 16, 0xf6bb4b60);
-    subround(H, b, c, d, a, 10, 23, 0xbebfbc70);
-    subround(H, a, b, c, d, 13, 4, 0x289b7ec6);
-    subround(H, d, a, b, c, 0, 11, 0xeaa127fa);
-    subround(H, c, d, a, b, 3, 16, 0xd4ef3085);
-    subround(H, b, c, d, a, 6, 23, 0x04881d05);
-    subround(H, a, b, c, d, 9, 4, 0xd9d4d039);
-    subround(H, d, a, b, c, 12, 11, 0xe6db99e5);
-    subround(H, c, d, a, b, 15, 16, 0x1fa27cf8);
-    subround(H, b, c, d, a, 2, 23, 0xc4ac5665);
-    subround(I, a, b, c, d, 0, 6, 0xf4292244);
-    subround(I, d, a, b, c, 7, 10, 0x432aff97);
-    subround(I, c, d, a, b, 14, 15, 0xab9423a7);
-    subround(I, b, c, d, a, 5, 21, 0xfc93a039);
-    subround(I, a, b, c, d, 12, 6, 0x655b59c3);
-    subround(I, d, a, b, c, 3, 10, 0x8f0ccc92);
-    subround(I, c, d, a, b, 10, 15, 0xffeff47d);
-    subround(I, b, c, d, a, 1, 21, 0x85845dd1);
-    subround(I, a, b, c, d, 8, 6, 0x6fa87e4f);
-    subround(I, d, a, b, c, 15, 10, 0xfe2ce6e0);
-    subround(I, c, d, a, b, 6, 15, 0xa3014314);
-    subround(I, b, c, d, a, 13, 21, 0x4e0811a1);
-    subround(I, a, b, c, d, 4, 6, 0xf7537e82);
-    subround(I, d, a, b, c, 11, 10, 0xbd3af235);
-    subround(I, c, d, a, b, 2, 15, 0x2ad7d2bb);
-    subround(I, b, c, d, a, 9, 21, 0xeb86d391);
-
-    s->h[0] += a;
-    s->h[1] += b;
-    s->h[2] += c;
-    s->h[3] += d;
+    size_t blkleft = sizeof(blk->block) - blk->used;
+    size_t chunk = *len < blkleft ? *len : blkleft;
+
+    const uint8_t *p = *vdata;
+    memcpy(blk->block + blk->used, p, chunk);
+    *vdata = p + chunk;
+    *len -= chunk;
+    blk->used += chunk;
+    blk->len += chunk;
+
+    if (blk->used == sizeof(blk->block)) {
+        blk->used = 0;
+        return true;
+    }
+
+    return false;
 }
 
-/* ----------------------------------------------------------------------
- * Outer MD5 algorithm: take an arbitrary length byte string,
- * convert it into 16-word blocks with the prescribed padding at
- * the end, and pass those blocks to the core MD5 algorithm.
- */
+static inline void md5_block_pad(md5_block *blk, BinarySink *bs)
+{
+    uint64_t final_len = blk->len << 3;
+    size_t pad = 63 & (55 - blk->used);
 
-#define BLKSIZE 64
+    put_byte(bs, 0x80);
+    put_padding(bs, pad, 0);
 
-static void MD5_BinarySink_write(BinarySink *bs, const void *data, size_t len);
+    unsigned char buf[8];
+    PUT_64BIT_LSB_FIRST(buf, final_len);
+    put_data(bs, buf, 8);
+    smemclr(buf, 8);
 
-void MD5Init(struct MD5Context *s)
-{
-    MD5_Core_Init(&s->core);
-    s->blkused = 0;
-    s->len = 0;
-    BinarySink_INIT(s, MD5_BinarySink_write);
+    assert(blk->used == 0 && "Should have exactly hit a block boundary");
 }
 
-static void MD5_BinarySink_write(BinarySink *bs, const void *data, size_t len)
+static inline uint32_t rol(uint32_t x, unsigned y)
 {
-    struct MD5Context *s = BinarySink_DOWNCAST(bs, struct MD5Context);
-    const unsigned char *q = (const unsigned char *)data;
-    uint32_t wordblock[16];
-    uint32_t lenw = len;
-    int i;
-
-    assert(lenw == len);
-
-    /*
-     * Update the length field.
-     */
-    s->len += lenw;
-
-    if (s->blkused + len < BLKSIZE) {
-        /*
-         * Trivial case: just add to the block.
-         */
-        memcpy(s->block + s->blkused, q, len);
-        s->blkused += len;
-    } else {
-        /*
-         * We must complete and process at least one block.
-         */
-        while (s->blkused + len >= BLKSIZE) {
-            memcpy(s->block + s->blkused, q, BLKSIZE - s->blkused);
-            q += BLKSIZE - s->blkused;
-            len -= BLKSIZE - s->blkused;
-            /* Now process the block. Gather bytes little-endian into words */
-            for (i = 0; i < 16; i++) {
-                wordblock[i] =
-                    (((uint32_t) s->block[i * 4 + 3]) << 24) |
-                    (((uint32_t) s->block[i * 4 + 2]) << 16) |
-                    (((uint32_t) s->block[i * 4 + 1]) << 8) |
-                    (((uint32_t) s->block[i * 4 + 0]) << 0);
-            }
-            MD5_Block(&s->core, wordblock);
-            s->blkused = 0;
-        }
-#ifdef MPEXT
-	if (len > 0)
-#endif
-        memcpy(s->block, q, len);
-        s->blkused = len;
-    }
+    return (x << (31 & y)) | (x >> (31 & -y));
 }
 
-void MD5Final(unsigned char output[16], struct MD5Context *s)
+static inline uint32_t Ch(uint32_t ctrl, uint32_t if1, uint32_t if0)
 {
-    int i;
-    unsigned pad;
-    unsigned char c[64];
-    uint64_t len;
-
-    if (s->blkused >= 56)
-        pad = 56 + 64 - s->blkused;
-    else
-        pad = 56 - s->blkused;
-
-    len = (s->len << 3);
-
-    memset(c, 0, pad);
-    c[0] = 0x80;
-    put_data(s, c, pad);
-
-    PUT_64BIT_LSB_FIRST(c, len);
+    return if0 ^ (ctrl & (if1 ^ if0));
+}
 
-    put_data(s, c, 8);
+/* Parameter functions for the four MD5 round types */
+static inline uint32_t F(uint32_t x, uint32_t y, uint32_t z)
+{ return Ch(x, y, z); }
+static inline uint32_t G(uint32_t x, uint32_t y, uint32_t z)
+{ return Ch(z, x, y); }
+static inline uint32_t H(uint32_t x, uint32_t y, uint32_t z)
+{ return x ^ y ^ z; }
+static inline uint32_t I(uint32_t x, uint32_t y, uint32_t z)
+{ return y ^ (x | ~z); }
+
+static inline void md5_round(
+    unsigned round_index, const uint32_t *message,
+    uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d,
+    uint32_t (*f)(uint32_t, uint32_t, uint32_t))
+{
+    struct md5_round_constant rc = md5_round_constants[round_index];
 
-    for (i = 0; i < 4; i++) {
-        output[4 * i + 3] = (s->core.h[i] >> 24) & 0xFF;
-        output[4 * i + 2] = (s->core.h[i] >> 16) & 0xFF;
-        output[4 * i + 1] = (s->core.h[i] >> 8) & 0xFF;
-        output[4 * i + 0] = (s->core.h[i] >> 0) & 0xFF;
-    }
+    *a = *b + rol(*a + f(*b, *c, *d) + message[rc.msg_index] + rc.addition,
+                  rc.rotation);
 }
 
-void MD5Simple(void const *p, unsigned len, unsigned char output[16])
+static void md5_do_block(uint32_t *core, const uint8_t *block)
 {
-    struct MD5Context s;
+    uint32_t message_words[16];
+    for (size_t i = 0; i < 16; i++)
+        message_words[i] = GET_32BIT_LSB_FIRST(block + 4*i);
+
+    uint32_t a = core[0], b = core[1], c = core[2], d = core[3];
+
+    size_t t = 0;
+    for (size_t u = 0; u < 4; u++) {
+        md5_round(t++, message_words, &a, &b, &c, &d, F);
+        md5_round(t++, message_words, &d, &a, &b, &c, F);
+        md5_round(t++, message_words, &c, &d, &a, &b, F);
+        md5_round(t++, message_words, &b, &c, &d, &a, F);
+    }
+    for (size_t u = 0; u < 4; u++) {
+        md5_round(t++, message_words, &a, &b, &c, &d, G);
+        md5_round(t++, message_words, &d, &a, &b, &c, G);
+        md5_round(t++, message_words, &c, &d, &a, &b, G);
+        md5_round(t++, message_words, &b, &c, &d, &a, G);
+    }
+    for (size_t u = 0; u < 4; u++) {
+        md5_round(t++, message_words, &a, &b, &c, &d, H);
+        md5_round(t++, message_words, &d, &a, &b, &c, H);
+        md5_round(t++, message_words, &c, &d, &a, &b, H);
+        md5_round(t++, message_words, &b, &c, &d, &a, H);
+    }
+    for (size_t u = 0; u < 4; u++) {
+        md5_round(t++, message_words, &a, &b, &c, &d, I);
+        md5_round(t++, message_words, &d, &a, &b, &c, I);
+        md5_round(t++, message_words, &c, &d, &a, &b, I);
+        md5_round(t++, message_words, &b, &c, &d, &a, I);
+    }
 
-    MD5Init(&s);
-    put_data(&s, (unsigned char const *)p, len);
-    MD5Final(output, &s);
-    smemclr(&s, sizeof(s));
-}
+    core[0] += a;
+    core[1] += b;
+    core[2] += c;
+    core[3] += d;
 
-/* ----------------------------------------------------------------------
- * Thin abstraction for things where hashes are pluggable.
- */
+    smemclr(message_words, sizeof(message_words));
+}
 
-struct md5_hash {
-    struct MD5Context state;
+typedef struct md5 {
+    uint32_t core[4];
+    md5_block blk;
+    BinarySink_IMPLEMENTATION;
     ssh_hash hash;
-};
+} md5;
+
+static void md5_write(BinarySink *bs, const void *vp, size_t len);
 
 static ssh_hash *md5_new(const ssh_hashalg *alg)
 {
-    struct md5_hash *h = snew(struct md5_hash);
-    MD5Init(&h->state);
-    h->hash.vt = alg;
-    BinarySink_DELEGATE_INIT(&h->hash, &h->state);
-    return &h->hash;
+    md5 *s = snew(md5);
+
+    s->hash.vt = alg;
+    BinarySink_INIT(s, md5_write);
+    BinarySink_DELEGATE_INIT(&s->hash, s);
+    return &s->hash;
 }
 
-static ssh_hash *md5_copy(ssh_hash *hashold)
+static void md5_reset(ssh_hash *hash)
 {
-    struct md5_hash *hold, *hnew;
-    ssh_hash *hashnew = md5_new(hashold->vt);
+    md5 *s = container_of(hash, md5, hash);
 
-    hold = container_of(hashold, struct md5_hash, hash);
-    hnew = container_of(hashnew, struct md5_hash, hash);
+    memcpy(s->core, md5_initial_state, sizeof(s->core));
+    md5_block_setup(&s->blk);
+}
 
-    hnew->state = hold->state;
-    BinarySink_COPIED(&hnew->state);
+static void md5_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+    md5 *copy = container_of(hcopy, md5, hash);
+    md5 *orig = container_of(horig, md5, hash);
 
-    return hashnew;
+    memcpy(copy, orig, sizeof(*copy));
+    BinarySink_COPIED(copy);
+    BinarySink_DELEGATE_INIT(&copy->hash, copy);
 }
 
 static void md5_free(ssh_hash *hash)
 {
-    struct md5_hash *h = container_of(hash, struct md5_hash, hash);
+    md5 *s = container_of(hash, md5, hash);
 
-    smemclr(h, sizeof(*h));
-    sfree(h);
+    smemclr(s, sizeof(*s));
+    sfree(s);
 }
 
-static void md5_final(ssh_hash *hash, unsigned char *output)
+static void md5_write(BinarySink *bs, const void *vp, size_t len)
 {
-    struct md5_hash *h = container_of(hash, struct md5_hash, hash);
-    MD5Final(output, &h->state);
-    md5_free(hash);
+    md5 *s = BinarySink_DOWNCAST(bs, md5);
+
+    while (len > 0)
+        if (md5_block_write(&s->blk, &vp, &len))
+            md5_do_block(s->core, s->blk.block);
+}
+
+static void md5_digest(ssh_hash *hash, uint8_t *digest)
+{
+    md5 *s = container_of(hash, md5, hash);
+
+    md5_block_pad(&s->blk, BinarySink_UPCAST(s));
+    for (size_t i = 0; i < 4; i++)
+        PUT_32BIT_LSB_FIRST(digest + 4*i, s->core[i]);
 }
 
 const ssh_hashalg ssh_md5 = {
-    md5_new, md5_copy, md5_final, md5_free, 16, 64, HASHALG_NAMES_BARE("MD5"),
+    .new = md5_new,
+    .reset = md5_reset,
+    .copyfrom = md5_copyfrom,
+    .digest = md5_digest,
+    .free = md5_free,
+    .hlen = 16,
+    .blocklen = 64,
+    HASHALG_NAMES_BARE("MD5"),
 };

+ 1 - 0
source/putty/sshppl.h

@@ -8,6 +8,7 @@
 #define PUTTY_SSHPPL_H
 
 typedef void (*packet_handler_fn_t)(PacketProtocolLayer *ppl, PktIn *pktin);
+typedef struct PacketProtocolLayerVtable PacketProtocolLayerVtable;
 
 struct PacketProtocolLayerVtable {
     void (*free)(PacketProtocolLayer *);

+ 30 - 35
source/putty/sshprng.c

@@ -7,7 +7,7 @@
 
 #include "putty.h"
 #include "ssh.h"
-#include "mpint.h"
+#include "mpint_i.h"
 
 #ifdef PRNG_DIAGNOSTICS
 #define prngdebug debug
@@ -55,17 +55,9 @@ struct prng_impl {
      * into it. The counter-mode generation is achieved by copying
      * that hash object, appending the counter value to the copy, and
      * calling ssh_hash_final.
-     *
-     * pending_output is a buffer of size equal to the hash length,
-     * which receives each of those hashes as it's generated. The
-     * bytes of the hash are returned in reverse order, just because
-     * that made it marginally easier to deal with the
-     * pending_output_remaining field.
      */
     ssh_hash *generator;
-    mp_int *counter;
-    uint8_t *pending_output;
-    size_t pending_output_remaining;
+    BignumInt counter[128 / BIGNUM_INT_BITS];
 
     /*
      * When re-seeding the generator, you call prng_seed_begin(),
@@ -121,9 +113,7 @@ prng *prng_new(const ssh_hashalg *hashalg)
     pi->hashalg = hashalg;
     pi->keymaker = NULL;
     pi->generator = NULL;
-    pi->pending_output = snewn(pi->hashalg->hlen, uint8_t);
-    pi->pending_output_remaining = 0;
-    pi->counter = mp_new(128);
+    memset(pi->counter, 0, sizeof(pi->counter));
     { // WINSCP
     size_t i; // WINSCP
     for (i = 0; i < NCOLLECTORS; i++)
@@ -141,8 +131,7 @@ void prng_free(prng *pr)
 {
     prng_impl *pi = container_of(pr, prng_impl, Prng);
 
-    sfree(pi->pending_output);
-    mp_free(pi->counter);
+    smemclr(pi->counter, sizeof(pi->counter));
     { // WINSCP
     size_t i; // WINSCP
     for (i = 0; i < NCOLLECTORS; i++)
@@ -190,6 +179,7 @@ static void prng_seed_BinarySink_write(
 void prng_seed_finish(prng *pr)
 {
     prng_impl *pi = container_of(pr, prng_impl, Prng);
+    unsigned char buf[MAX_HASH_LEN];
 
     assert(pi->keymaker);
 
@@ -198,7 +188,7 @@ void prng_seed_finish(prng *pr)
     /*
      * Actually generate the key.
      */
-    ssh_hash_final(pi->keymaker, pi->pending_output);
+    ssh_hash_final(pi->keymaker, buf);
     pi->keymaker = NULL;
 
     /*
@@ -207,30 +197,32 @@ void prng_seed_finish(prng *pr)
      */
     assert(!pi->generator);
     pi->generator = ssh_hash_new(pi->hashalg);
-    put_data(pi->generator, pi->pending_output, pi->hashalg->hlen);
-    smemclr(pi->pending_output, pi->hashalg->hlen);
+    put_data(pi->generator, buf, pi->hashalg->hlen);
 
     pi->until_reseed = RESEED_DATA_SIZE;
     pi->last_reseed_time = prng_reseed_time_ms();
-    pi->pending_output_remaining = 0;
+
+    smemclr(buf, sizeof(buf));
 }
 
-static inline void prng_generate(prng_impl *pi)
+static inline void prng_generate(prng_impl *pi, void *outbuf)
 {
     ssh_hash *h = ssh_hash_copy(pi->generator);
 
     prngdebug("prng_generate\n");
-
     put_byte(h, 'G');
-    put_mp_ssh2(h, pi->counter);
-    mp_add_integer_into(pi->counter, pi->counter, 1);
-    ssh_hash_final(h, pi->pending_output);
-    pi->pending_output_remaining = pi->hashalg->hlen;
+    for (unsigned i = 0; i < 128; i += 8)
+        put_byte(h, pi->counter[i/BIGNUM_INT_BITS] >> (i%BIGNUM_INT_BITS));
+    BignumCarry c = 1;
+    for (unsigned i = 0; i < lenof(pi->counter); i++)
+        BignumADC(pi->counter[i], c, pi->counter[i], 0, c);
+    ssh_hash_final(h, outbuf);
 }
 
 void prng_read(prng *pr, void *vout, size_t size)
 {
     prng_impl *pi = container_of(pr, prng_impl, Prng);
+    unsigned char buf[MAX_HASH_LEN];
 
     assert(!pi->keymaker);
 
@@ -238,14 +230,16 @@ void prng_read(prng *pr, void *vout, size_t size)
 
     { // WINSCP
     uint8_t *out = (uint8_t *)vout;
-    for (; size > 0; size--) {
-        if (pi->pending_output_remaining == 0)
-            prng_generate(pi);
-        pi->pending_output_remaining--;
-        *out++ = pi->pending_output[pi->pending_output_remaining];
-        pi->pending_output[pi->pending_output_remaining] = 0;
+    while (size > 0) {
+        prng_generate(pi, buf);
+        size_t to_use = size > pi->hashalg->hlen ? pi->hashalg->hlen : size;
+        memcpy(out, buf, to_use);
+        out += to_use;
+        size -= to_use;
     }
 
+    smemclr(buf, sizeof(buf));
+
     prng_seed_begin(&pi->Prng);
     prng_seed_finish(&pi->Prng);
     } // WINSCP
@@ -278,20 +272,21 @@ void prng_add_entropy(prng *pr, unsigned source_id, ptrlen data)
         prng_seed_begin(&pi->Prng);
 
         { // WINSCP
+        unsigned char buf[MAX_HASH_LEN];
         uint32_t reseed_index = ++pi->reseeds;
         prngdebug("prng entropy reseed #%"PRIu32"\n", reseed_index);
         { // WINSCP
         size_t i; // WINSCP
         for (i = 0; i < NCOLLECTORS; i++) {
             prngdebug("emptying collector %"SIZEu"\n", i);
-            ssh_hash_final(pi->collectors[i], pi->pending_output);
-            put_data(&pi->Prng, pi->pending_output, pi->hashalg->hlen);
-            pi->collectors[i] = ssh_hash_new(pi->hashalg);
+            ssh_hash_digest(pi->collectors[i], buf);
+            put_data(&pi->Prng, buf, pi->hashalg->hlen);
+            ssh_hash_reset(pi->collectors[i]);
             if (reseed_index & 1)
                 break;
             reseed_index >>= 1;
         }
-
+        smemclr(buf, sizeof(buf));
         prng_seed_finish(&pi->Prng);
         } // WINSCP
         } // WINSCP

Різницю між файлами не показано, бо вона завелика
+ 448 - 300
source/putty/sshpubk.c


+ 3 - 3
source/putty/sshrand.c

@@ -19,7 +19,7 @@ int random_active = 0;
  */
 void random_add_noise(NoiseSourceId source, const void *noise, int length) { }
 void random_ref(void) { }
-void random_setup_special(void) { }
+void random_setup_custom(const ssh_hashalg *hash) { }
 void random_unref(void) { }
 void random_read(void *out, size_t size)
 {
@@ -106,12 +106,12 @@ void random_ref(void)
     WINSCP_PUTTY_SECTION_LEAVE;
 }
 
-void random_setup_special()
+void random_setup_custom(const ssh_hashalg *hash)
 {
     unreachable_internal(); // used by PuTTYgen only
     WINSCP_PUTTY_SECTION_ENTER;
     random_active++;
-    random_create(&ssh_sha512);
+    random_create(hash);
     WINSCP_PUTTY_SECTION_LEAVE;
 }
 

+ 108 - 37
source/putty/sshrsa.c

@@ -43,6 +43,21 @@ void BinarySource_get_rsa_ssh1_priv(
     rsa->private_exponent = get_mp_ssh1(src);
 }
 
+key_components *rsa_components(RSAKey *rsa)
+{
+    key_components *kc = key_components_new();
+    key_components_add_text(kc, "key_type", "RSA");
+    key_components_add_mp(kc, "public_modulus", rsa->modulus);
+    key_components_add_mp(kc, "public_exponent", rsa->exponent);
+    if (rsa->private_exponent) {
+        key_components_add_mp(kc, "private_exponent", rsa->private_exponent);
+        key_components_add_mp(kc, "private_p", rsa->p);
+        key_components_add_mp(kc, "private_q", rsa->q);
+        key_components_add_mp(kc, "private_inverse_q_mod_p", rsa->iqmp);
+    }
+    return kc;
+}
+
 RSAKey *BinarySource_get_rsa_ssh1_priv_agent(BinarySource *src)
 {
     RSAKey *rsa = snew(RSAKey);
@@ -132,8 +147,8 @@ bool rsa_ssh1_encrypt(unsigned char *data, int length, RSAKey *key)
  * Uses Chinese Remainder Theorem to speed computation up over the
  * obvious implementation of a single big modpow.
  */
-mp_int *crt_modpow(mp_int *base, mp_int *exp, mp_int *mod,
-                      mp_int *p, mp_int *q, mp_int *iqmp)
+static mp_int *crt_modpow(mp_int *base, mp_int *exp, mp_int *mod,
+                          mp_int *p, mp_int *q, mp_int *iqmp)
 {
     mp_int *pm1, *qm1, *pexp, *qexp, *presult, *qresult;
     mp_int *diff, *multiplier, *ret0, *ret;
@@ -320,6 +335,19 @@ char *rsa_ssh1_fingerprint(RSAKey *key)
     return strbuf_to_str(out);
 }
 
+/*
+ * Wrap the output of rsa_ssh1_fingerprint up into the same kind of
+ * structure that comes from ssh2_all_fingerprints.
+ */
+char **rsa_ssh1_fake_all_fingerprints(RSAKey *key)
+{
+    char **ret = snewn(SSH_N_FPTYPES, char *);
+    for (unsigned i = 0; i < SSH_N_FPTYPES; i++)
+        ret[i] = NULL;
+    ret[SSH_FPTYPE_MD5] = rsa_ssh1_fingerprint(key);
+    return ret;
+}
+
 /*
  * Verify that the public data in an RSA key matches the private
  * data. We also check the private data itself: we ensure that p >
@@ -392,6 +420,15 @@ void rsa_ssh1_public_blob(BinarySink *bs, RSAKey *key,
     }
 }
 
+void rsa_ssh1_private_blob_agent(BinarySink *bs, RSAKey *key)
+{
+    rsa_ssh1_public_blob(bs, key, RSA_SSH1_MODULUS_FIRST);
+    put_mp_ssh1(bs, key->private_exponent);
+    put_mp_ssh1(bs, key->iqmp);
+    put_mp_ssh1(bs, key->q);
+    put_mp_ssh1(bs, key->p);
+}
+
 /* Given an SSH-1 public key blob, determine its length. */
 int rsa_ssh1_public_blob_len(ptrlen data)
 {
@@ -450,9 +487,13 @@ void freersakey(RSAKey *key)
 }
 
 /* ----------------------------------------------------------------------
- * Implementation of the ssh-rsa signing key type.
+ * Implementation of the ssh-rsa signing key type family.
  */
 
+struct ssh2_rsa_extra {
+    unsigned signflags;
+};
+
 static void rsa2_freekey(ssh_key *key);   /* forward reference */
 
 static ssh_key *rsa2_new_pub(const ssh_keyalg *self, ptrlen data)
@@ -465,7 +506,7 @@ static ssh_key *rsa2_new_pub(const ssh_keyalg *self, ptrlen data)
         return NULL;
 
     rsa = snew(RSAKey);
-    rsa->sshk.vt = &ssh_rsa;
+    rsa->sshk.vt = self;
     rsa->exponent = get_mp_ssh2(src);
     rsa->modulus = get_mp_ssh2(src);
     rsa->private_exponent = NULL;
@@ -493,6 +534,12 @@ static char *rsa2_cache_str(ssh_key *key)
     return rsastr_fmt(rsa);
 }
 
+static key_components *rsa2_components(ssh_key *key)
+{
+    RSAKey *rsa = container_of(key, RSAKey, sshk);
+    return rsa_components(rsa);
+}
+
 static void rsa2_public_blob(ssh_key *key, BinarySink *bs)
 {
     RSAKey *rsa = container_of(key, RSAKey, sshk);
@@ -719,8 +766,10 @@ static bool rsa2_verify(ssh_key *key, ptrlen sig, ptrlen data)
     ptrlen type, in_pl;
     mp_int *in, *out;
 
-    /* If we need to support variable flags on verify, this is where they go */
-    const ssh_hashalg *halg = rsa2_hash_alg_for_flags(0, NULL);
+    const struct ssh2_rsa_extra *extra =
+        (const struct ssh2_rsa_extra *)key->vt->extra;
+
+    const ssh_hashalg *halg = rsa2_hash_alg_for_flags(extra->signflags, NULL);
 
     /* Start by making sure the key is even long enough to encode a
      * signature. If not, everything fails to verify. */
@@ -741,7 +790,7 @@ static bool rsa2_verify(ssh_key *key, ptrlen sig, ptrlen data)
      * mp_from_bytes_be, which will tolerate anything.
      */
     in_pl = get_string(src);
-    if (get_err(src) || !ptrlen_eq_string(type, "ssh-rsa"))
+    if (get_err(src) || !ptrlen_eq_string(type, key->vt->ssh_id))
         return false;
 
     in = mp_from_bytes_be(in_pl);
@@ -774,6 +823,10 @@ static void rsa2_sign(ssh_key *key, ptrlen data,
     const ssh_hashalg *halg;
     const char *sign_alg_name;
 
+    const struct ssh2_rsa_extra *extra =
+        (const struct ssh2_rsa_extra *)key->vt->extra;
+    flags |= extra->signflags;
+
     halg = rsa2_hash_alg_for_flags(flags, &sign_alg_name);
 
     nbytes = (mp_get_nbits(rsa->modulus) + 7) / 8;
@@ -798,7 +851,7 @@ static void rsa2_sign(ssh_key *key, ptrlen data,
     mp_free(out);
 }
 
-char *rsa2_invalid(ssh_key *key, unsigned flags)
+static char *rsa2_invalid(ssh_key *key, unsigned flags)
 {
     RSAKey *rsa = container_of(key, RSAKey, sshk);
     size_t bits = mp_get_nbits(rsa->modulus), nbytes = (bits + 7) / 8;
@@ -813,26 +866,46 @@ char *rsa2_invalid(ssh_key *key, unsigned flags)
     return NULL;
 }
 
+static const struct ssh2_rsa_extra
+    rsa_extra = { 0 },
+    rsa_sha256_extra = { SSH_AGENT_RSA_SHA2_256 },
+    rsa_sha512_extra = { SSH_AGENT_RSA_SHA2_512 };
+
+#define COMMON_KEYALG_FIELDS                    \
+    .new_pub = rsa2_new_pub,                    \
+    .new_priv = rsa2_new_priv,                  \
+    .new_priv_openssh = rsa2_new_priv_openssh,  \
+    .freekey = rsa2_freekey,                    \
+    .invalid = rsa2_invalid,                    \
+    .sign = rsa2_sign,                          \
+    .verify = rsa2_verify,                      \
+    .public_blob = rsa2_public_blob,            \
+    .private_blob = rsa2_private_blob,          \
+    .openssh_blob = rsa2_openssh_blob,          \
+    .cache_str = rsa2_cache_str,                \
+    .components = rsa2_components,              \
+    .pubkey_bits = rsa2_pubkey_bits,            \
+    .cache_id = "rsa2"
+
 const ssh_keyalg ssh_rsa = {
-    rsa2_new_pub,
-    rsa2_new_priv,
-    rsa2_new_priv_openssh,
-
-    rsa2_freekey,
-    rsa2_invalid,
-    rsa2_sign,
-    rsa2_verify,
-    rsa2_public_blob,
-    rsa2_private_blob,
-    rsa2_openssh_blob,
-    rsa2_cache_str,
-
-    rsa2_pubkey_bits,
-
-    "ssh-rsa",
-    "rsa2",
-    NULL,
-    SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512,
+    COMMON_KEYALG_FIELDS,
+    .ssh_id = "ssh-rsa",
+    .supported_flags = SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512,
+    .extra = &rsa_extra,
+};
+
+const ssh_keyalg ssh_rsa_sha256 = {
+    COMMON_KEYALG_FIELDS,
+    .ssh_id = "rsa-sha2-256",
+    .supported_flags = 0,
+    .extra = &rsa_sha256_extra,
+};
+
+const ssh_keyalg ssh_rsa_sha512 = {
+    COMMON_KEYALG_FIELDS,
+    .ssh_id = "rsa-sha2-512",
+    .supported_flags = 0,
+    .extra = &rsa_sha512_extra,
 };
 
 RSAKey *ssh_rsakex_newkey(ptrlen data)
@@ -859,16 +932,17 @@ static void oaep_mask(const ssh_hashalg *h, void *seed, int seedlen,
     unsigned char *data = (unsigned char *)vdata;
     unsigned count = 0;
 
+    ssh_hash *s = ssh_hash_new(h);
+
     while (datalen > 0) {
         int i, max = (datalen > h->hlen ? h->hlen : datalen);
-        ssh_hash *s;
         unsigned char hash[MAX_HASH_LEN];
 
+        ssh_hash_reset(s);
         assert(h->hlen <= MAX_HASH_LEN);
-        s = ssh_hash_new(h);
         put_data(s, seed, seedlen);
         put_uint32(s, count);
-        ssh_hash_final(s, hash);
+        ssh_hash_digest(s, hash);
         count++;
 
         for (i = 0; i < max; i++)
@@ -877,6 +951,8 @@ static void oaep_mask(const ssh_hashalg *h, void *seed, int seedlen,
         data += max;
         datalen -= max;
     }
+
+    ssh_hash_free(s);
 }
 
 strbuf *ssh_rsakex_encrypt(RSAKey *rsa, const ssh_hashalg *h, ptrlen in)
@@ -935,10 +1011,7 @@ strbuf *ssh_rsakex_encrypt(RSAKey *rsa, const ssh_hashalg *h, ptrlen in)
     random_read(out + 1, HLEN);
     /* At position 1+HLEN, the data block DB, consisting of: */
     /* The hash of the label (we only support an empty label here) */
-    {
-        ssh_hash *s = ssh_hash_new(h);
-        ssh_hash_final(s, out + HLEN + 1);
-    }
+    hash_simple(h, PTRLEN_LITERAL(""), out + HLEN + 1);
     /* A bunch of zero octets */
     memset(out + 2*HLEN + 1, 0, outlen - (2*HLEN + 1));
     /* A single 1 octet, followed by the input message data. */
@@ -982,7 +1055,6 @@ mp_int *ssh_rsakex_decrypt(
     int outlen, i;
     unsigned char *out;
     unsigned char labelhash[64];
-    ssh_hash *hash;
     BinarySource src[1];
     const int HLEN = h->hlen;
 
@@ -1016,8 +1088,7 @@ mp_int *ssh_rsakex_decrypt(
     }
     /* Check the label hash at position 1+HLEN */
     assert(HLEN <= lenof(labelhash));
-    hash = ssh_hash_new(h);
-    ssh_hash_final(hash, labelhash);
+    hash_simple(h, PTRLEN_LITERAL(""), labelhash);
     if (memcmp(out + HLEN + 1, labelhash, HLEN)) {
         sfree(out);
         return NULL;

+ 82 - 51
source/putty/sshsh256.c

@@ -105,8 +105,10 @@ static ssh_hash *sha256_select(const ssh_hashalg *alg)
 }
 
 const ssh_hashalg ssh_sha256 = {
-    sha256_select, NULL, NULL, NULL,
-    32, 64, HASHALG_NAMES_ANNOTATED("SHA-256", "dummy selector vtable"),
+    .new = sha256_select,
+    .hlen = 32,
+    .blocklen = 64,
+    HASHALG_NAMES_ANNOTATED("SHA-256", "dummy selector vtable"),
 };
 
 #else
@@ -300,26 +302,28 @@ static ssh_hash *sha256_sw_new(const ssh_hashalg *alg)
 {
     sha256_sw *s = snew(sha256_sw);
 
-    memcpy(s->core, sha256_initial_state, sizeof(s->core));
-
-    sha256_block_setup(&s->blk);
-
     s->hash.vt = alg;
     BinarySink_INIT(s, sha256_sw_write);
     BinarySink_DELEGATE_INIT(&s->hash, s);
     return &s->hash;
 }
 
-static ssh_hash *sha256_sw_copy(ssh_hash *hash)
+static void sha256_sw_reset(ssh_hash *hash)
 {
     sha256_sw *s = container_of(hash, sha256_sw, hash);
-    sha256_sw *copy = snew(sha256_sw);
 
-    memcpy(copy, s, sizeof(*copy));
+    memcpy(s->core, sha256_initial_state, sizeof(s->core));
+    sha256_block_setup(&s->blk);
+}
+
+static void sha256_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+    sha256_sw *copy = container_of(hcopy, sha256_sw, hash);
+    sha256_sw *orig = container_of(horig, sha256_sw, hash);
+
+    memcpy(copy, orig, sizeof(*copy));
     BinarySink_COPIED(copy);
     BinarySink_DELEGATE_INIT(&copy->hash, copy);
-
-    return &copy->hash;
 }
 
 static void sha256_sw_free(ssh_hash *hash)
@@ -339,7 +343,7 @@ static void sha256_sw_write(BinarySink *bs, const void *vp, size_t len)
             sha256_sw_block(s->core, s->blk.block);
 }
 
-static void sha256_sw_final(ssh_hash *hash, uint8_t *digest)
+static void sha256_sw_digest(ssh_hash *hash, uint8_t *digest)
 {
     sha256_sw *s = container_of(hash, sha256_sw, hash);
 
@@ -348,13 +352,18 @@ static void sha256_sw_final(ssh_hash *hash, uint8_t *digest)
     size_t i; // WINSCP
     for (i = 0; i < 8; i++)
         PUT_32BIT_MSB_FIRST(digest + 4*i, s->core[i]);
-    sha256_sw_free(hash);
     } // WINSCP
 }
 
 const ssh_hashalg ssh_sha256_sw = {
-    sha256_sw_new, sha256_sw_copy, sha256_sw_final, sha256_sw_free,
-    32, 64, HASHALG_NAMES_BARE("SHA-256"), // WINSCP (removed "unaccelerated" annotation)
+    .new = sha256_sw_new,
+    .reset = sha256_sw_reset,
+    .copyfrom = sha256_sw_copyfrom,
+    .digest = sha256_sw_digest,
+    .free = sha256_sw_free,
+    .hlen = 32,
+    .blocklen = 64,
+    HASHALG_NAMES_BARE("SHA-256"), // WINSCP (removed "unaccelerated" annotation)
 };
 #endif // !WINSCP_VS
 
@@ -632,13 +641,24 @@ static sha256_ni *sha256_ni_alloc(void)
     return s;
 }
 
-FUNC_ISA /*WINSCP static*/ ssh_hash *sha256_ni_new(const ssh_hashalg *alg)
+/*WINSCP static*/ ssh_hash *sha256_ni_new(const ssh_hashalg *alg)
 {
     if (!sha256_hw_available_cached())
         return NULL;
 
     sha256_ni *s = sha256_ni_alloc();
 
+    s->hash.vt = alg;
+    BinarySink_INIT(s, sha256_ni_write);
+    BinarySink_DELEGATE_INIT(&s->hash, s);
+
+    return &s->hash;
+}
+
+FUNC_ISA static void sha256_ni_reset(ssh_hash *hash)
+{
+    sha256_ni *s = container_of(hash, sha256_ni, hash);
+
     /* Initialise the core vectors in their storage order */
     s->core[0] = _mm_set_epi64x(
         0x6a09e667bb67ae85ULL, 0x510e527f9b05688cULL);
@@ -646,26 +666,19 @@ FUNC_ISA /*WINSCP static*/ ssh_hash *sha256_ni_new(const ssh_hashalg *alg)
         0x3c6ef372a54ff53aULL, 0x1f83d9ab5be0cd19ULL);
 
     sha256_block_setup(&s->blk);
-
-    s->hash.vt = alg;
-    BinarySink_INIT(s, sha256_ni_write);
-    BinarySink_DELEGATE_INIT(&s->hash, s);
-    return &s->hash;
 }
 
-/*WINSCP static*/ ssh_hash *sha256_ni_copy(ssh_hash *hash)
+/*WINSCP static*/ void sha256_ni_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
 {
-    sha256_ni *s = container_of(hash, sha256_ni, hash);
-    sha256_ni *copy = sha256_ni_alloc();
+    sha256_ni *copy = container_of(hcopy, sha256_ni, hash);
+    sha256_ni *orig = container_of(horig, sha256_ni, hash);
 
     void *ptf_save = copy->pointer_to_free;
-    *copy = *s; /* structure copy */
+    *copy = *orig; /* structure copy */
     copy->pointer_to_free = ptf_save;
 
     BinarySink_COPIED(copy);
     BinarySink_DELEGATE_INIT(&copy->hash, copy);
-
-    return &copy->hash;
 }
 
 /*WINSCP static*/ void sha256_ni_free(ssh_hash *hash)
@@ -686,7 +699,7 @@ static void sha256_ni_write(BinarySink *bs, const void *vp, size_t len)
             sha256_ni_block(s->core, s->blk.block);
 }
 
-FUNC_ISA /*WINSCP static*/ void sha256_ni_final(ssh_hash *hash, uint8_t *digest)
+FUNC_ISA /*WINSCP static*/ void sha256_ni_digest(ssh_hash *hash, uint8_t *digest)
 {
     sha256_ni *s = container_of(hash, sha256_ni, hash);
 
@@ -707,8 +720,6 @@ FUNC_ISA /*WINSCP static*/ void sha256_ni_final(ssh_hash *hash, uint8_t *digest)
     __m128i *output = (__m128i *)digest;
     _mm_storeu_si128(output, dcba);
     _mm_storeu_si128(output+1, hgfe);
-
-    sha256_ni_free(hash);
 }
 
 #endif // WINSCP_VS
@@ -721,8 +732,14 @@ void sha256_ni_final(ssh_hash *hash, uint8_t *digest);
 void sha256_ni_free(ssh_hash *hash);
 
 const ssh_hashalg ssh_sha256_hw = {
-    sha256_ni_new, sha256_ni_copy, sha256_ni_final, sha256_ni_free,
-    32, 64, HASHALG_NAMES_ANNOTATED("SHA-256", "SHA-NI accelerated"),
+    .new = sha256_ni_new,
+    .reset = sha256_ni_reset,
+    .copyfrom = sha256_ni_copyfrom,
+    .digest = sha256_ni_digest,
+    .free = sha256_ni_free,
+    .hlen = 32,
+    .blocklen = 64,
+    HASHALG_NAMES_ANNOTATED("SHA-256", "SHA-NI accelerated"),
 };
 
 /* ----------------------------------------------------------------------
@@ -857,28 +874,31 @@ static ssh_hash *sha256_neon_new(const ssh_hashalg *alg)
 
     sha256_neon *s = snew(sha256_neon);
 
-    s->core.abcd = vld1q_u32(sha256_initial_state);
-    s->core.efgh = vld1q_u32(sha256_initial_state + 4);
-
-    sha256_block_setup(&s->blk);
-
     s->hash.vt = alg;
     BinarySink_INIT(s, sha256_neon_write);
     BinarySink_DELEGATE_INIT(&s->hash, s);
     return &s->hash;
 }
 
-static ssh_hash *sha256_neon_copy(ssh_hash *hash)
+static void sha256_neon_reset(ssh_hash *hash)
 {
     sha256_neon *s = container_of(hash, sha256_neon, hash);
-    sha256_neon *copy = snew(sha256_neon);
 
-    *copy = *s; /* structure copy */
+    s->core.abcd = vld1q_u32(sha256_initial_state);
+    s->core.efgh = vld1q_u32(sha256_initial_state + 4);
+
+    sha256_block_setup(&s->blk);
+}
+
+static void sha256_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+    sha256_neon *copy = container_of(hcopy, sha256_neon, hash);
+    sha256_neon *orig = container_of(horig, sha256_neon, hash);
+
+    *copy = *orig; /* structure copy */
 
     BinarySink_COPIED(copy);
     BinarySink_DELEGATE_INIT(&copy->hash, copy);
-
-    return &copy->hash;
 }
 
 static void sha256_neon_free(ssh_hash *hash)
@@ -897,19 +917,24 @@ static void sha256_neon_write(BinarySink *bs, const void *vp, size_t len)
             sha256_neon_block(&s->core, s->blk.block);
 }
 
-static void sha256_neon_final(ssh_hash *hash, uint8_t *digest)
+static void sha256_neon_digest(ssh_hash *hash, uint8_t *digest)
 {
     sha256_neon *s = container_of(hash, sha256_neon, hash);
 
     sha256_block_pad(&s->blk, BinarySink_UPCAST(s));
     vst1q_u8(digest,      vrev32q_u8(vreinterpretq_u8_u32(s->core.abcd)));
     vst1q_u8(digest + 16, vrev32q_u8(vreinterpretq_u8_u32(s->core.efgh)));
-    sha256_neon_free(hash);
 }
 
 const ssh_hashalg ssh_sha256_hw = {
-    sha256_neon_new, sha256_neon_copy, sha256_neon_final, sha256_neon_free,
-    32, 64, HASHALG_NAMES_ANNOTATED("SHA-256", "NEON accelerated"),
+    .new = sha256_neon_new,
+    .reset = sha256_neon_reset,
+    .copyfrom = sha256_neon_copyfrom,
+    .digest = sha256_neon_digest,
+    .free = sha256_neon_free,
+    .hlen = 32,
+    .blocklen = 64,
+    HASHALG_NAMES_ANNOTATED("SHA-256", "NEON accelerated"),
 };
 
 #endif
@@ -938,14 +963,20 @@ static ssh_hash *sha256_stub_new(const ssh_hashalg *alg)
 
 #define STUB_BODY { unreachable("Should never be called"); }
 
-static ssh_hash *sha256_stub_copy(ssh_hash *hash) { STUB_BODY; return NULL; }
+static void sha256_stub_reset(ssh_hash *hash) STUB_BODY
+static void sha256_stub_copyfrom(ssh_hash *hash, ssh_hash *orig) STUB_BODY
 static void sha256_stub_free(ssh_hash *hash) STUB_BODY
-static void sha256_stub_final(ssh_hash *hash, uint8_t *digest) STUB_BODY
+static void sha256_stub_digest(ssh_hash *hash, uint8_t *digest) STUB_BODY
 
 const ssh_hashalg ssh_sha256_hw = {
-    sha256_stub_new, sha256_stub_copy, sha256_stub_final, sha256_stub_free,
-    32, 64, HASHALG_NAMES_ANNOTATED(
-        "SHA-256", "!NONEXISTENT ACCELERATED VERSION!"),
+    .new = sha256_stub_new,
+    .reset = sha256_stub_reset,
+    .copyfrom = sha256_stub_copyfrom,
+    .digest = sha256_stub_digest,
+    .free = sha256_stub_free,
+    .hlen = 32,
+    .blocklen = 64,
+    HASHALG_NAMES_ANNOTATED("SHA-256", "!NONEXISTENT ACCELERATED VERSION!"),
 };
 
 #endif // !WINSCP_VS

+ 752 - 285
source/putty/sshsh512.c

@@ -9,361 +9,828 @@
 #include <assert.h>
 #include "ssh.h"
 
-#define BLKSIZE 128
+/*
+ * Start by deciding whether we can support hardware SHA at all.
+ */
+#define HW_SHA512_NONE 0
+#define HW_SHA512_NEON 1
+
+#ifdef _FORCE_SHA512_NEON
+#   define HW_SHA512 HW_SHA512_NEON
+#elif defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+    /* Arm can potentially support both endiannesses, but this code
+     * hasn't been tested on anything but little. If anyone wants to
+     * run big-endian, they'll need to fix it first. */
+#elif defined __ARM_FEATURE_SHA512
+    /* If the Arm SHA-512 extension is available already, we can
+     * support NEON SHA without having to enable anything by hand */
+#   define HW_SHA512 HW_SHA512_NEON
+#elif defined(__clang__)
+#   if __has_attribute(target) && __has_include(<arm_neon.h>) &&       \
+    (defined(__aarch64__))
+        /* clang can enable the crypto extension in AArch64 using
+         * __attribute__((target)) */
+#       define HW_SHA512 HW_SHA512_NEON
+#       define USE_CLANG_ATTR_TARGET_AARCH64
+#   endif
+#endif
+
+#if defined _FORCE_SOFTWARE_SHA || !defined HW_SHA512
+#   undef HW_SHA512
+#   define HW_SHA512 HW_SHA512_NONE
+#endif
 
-typedef struct {
-    uint64_t h[8];
-    unsigned char block[BLKSIZE];
-    int blkused;
-    uint64_t lenhi, lenlo;
-    BinarySink_IMPLEMENTATION;
-} SHA512_State;
+/*
+ * The actual query function that asks if hardware acceleration is
+ * available.
+ */
+static bool sha512_hw_available(void);
 
 /*
- * Arithmetic implementations. Note that AND, XOR and NOT can
- * overlap destination with one source, but the others can't.
+ * The top-level selection function, caching the results of
+ * sha512_hw_available() so it only has to run once.
  */
-#define add(r,x,y) ( r = (x) + (y) )
-#define rorB(r,x,y) ( r = ((x) >> (y)) | ((x) << (64-(y))) )
-#define rorL(r,x,y) ( r = ((x) >> (y)) | ((x) << (64-(y))) )
-#define shrB(r,x,y) ( r = (x) >> (y) )
-#define shrL(r,x,y) ( r = (x) >> (y) )
-#define and(r,x,y) ( r = (x) & (y) )
-#define xor(r,x,y) ( r = (x) ^ (y) )
-#define not(r,x) ( r = ~(x) )
-#define INIT(h,l) ((((uint64_t)(h)) << 32) | (l))
-#define BUILD(r,h,l) ( r = ((((uint64_t)(h)) << 32) | (l)) )
-#define EXTRACT(h,l,r) ( h = (r) >> 32, l = (r) & 0xFFFFFFFFU )
+static bool sha512_hw_available_cached(void)
+{
+    static bool initialised = false;
+    static bool hw_available;
+    if (!initialised) {
+        hw_available = sha512_hw_available();
+        initialised = true;
+    }
+    return hw_available;
+}
+
+struct sha512_select_options {
+    const ssh_hashalg *hw, *sw;
+};
+
+static ssh_hash *sha512_select(const ssh_hashalg *alg)
+{
+    const struct sha512_select_options *options =
+        (const struct sha512_select_options *)alg->extra;
+
+    const ssh_hashalg *real_alg =
+        sha512_hw_available_cached() ? options->hw : options->sw;
+
+    return ssh_hash_new(real_alg);
+}
+
+const struct sha512_select_options ssh_sha512_select_options = {
+    &ssh_sha512_hw, &ssh_sha512_sw,
+};
+const struct sha512_select_options ssh_sha384_select_options = {
+    &ssh_sha384_hw, &ssh_sha384_sw,
+};
+
+const ssh_hashalg ssh_sha512 = {
+    .new = sha512_select,
+    .hlen = 64,
+    .blocklen = 128,
+    HASHALG_NAMES_ANNOTATED("SHA-512", "dummy selector vtable"),
+    .extra = &ssh_sha512_select_options,
+};
+
+const ssh_hashalg ssh_sha384 = {
+    .new = sha512_select,
+    .hlen = 48,
+    .blocklen = 128,
+    HASHALG_NAMES_ANNOTATED("SHA-384", "dummy selector vtable"),
+    .extra = &ssh_sha384_select_options,
+};
 
 /* ----------------------------------------------------------------------
- * Core SHA512 algorithm: processes 16-doubleword blocks into a
- * message digest.
+ * Definitions likely to be helpful to multiple implementations.
  */
 
-#define Ch(r,t,x,y,z) ( not(t,x), and(r,t,z), and(t,x,y), xor(r,r,t) )
-#define Maj(r,t,x,y,z) ( and(r,x,y), and(t,x,z), xor(r,r,t), \
-                         and(t,y,z), xor(r,r,t) )
-#define bigsigma0(r,t,x) ( rorL(r,x,28), rorB(t,x,34), xor(r,r,t), \
-                           rorB(t,x,39), xor(r,r,t) )
-#define bigsigma1(r,t,x) ( rorL(r,x,14), rorL(t,x,18), xor(r,r,t), \
-                           rorB(t,x,41), xor(r,r,t) )
-#define smallsigma0(r,t,x) ( rorL(r,x,1), rorL(t,x,8), xor(r,r,t), \
-                             shrL(t,x,7), xor(r,r,t) )
-#define smallsigma1(r,t,x) ( rorL(r,x,19), rorB(t,x,61), xor(r,r,t), \
-                             shrL(t,x,6), xor(r,r,t) )
-
-static void SHA512_Core_Init(SHA512_State *s) {
-    static const uint64_t iv[] = {
-        INIT(0x6a09e667, 0xf3bcc908),
-        INIT(0xbb67ae85, 0x84caa73b),
-        INIT(0x3c6ef372, 0xfe94f82b),
-        INIT(0xa54ff53a, 0x5f1d36f1),
-        INIT(0x510e527f, 0xade682d1),
-        INIT(0x9b05688c, 0x2b3e6c1f),
-        INIT(0x1f83d9ab, 0xfb41bd6b),
-        INIT(0x5be0cd19, 0x137e2179),
-    };
-    int i;
-    for (i = 0; i < 8; i++)
-        s->h[i] = iv[i];
-}
-
-static void SHA384_Core_Init(SHA512_State *s) {
-    static const uint64_t iv[] = {
-        INIT(0xcbbb9d5d, 0xc1059ed8),
-        INIT(0x629a292a, 0x367cd507),
-        INIT(0x9159015a, 0x3070dd17),
-        INIT(0x152fecd8, 0xf70e5939),
-        INIT(0x67332667, 0xffc00b31),
-        INIT(0x8eb44a87, 0x68581511),
-        INIT(0xdb0c2e0d, 0x64f98fa7),
-        INIT(0x47b5481d, 0xbefa4fa4),
-    };
-    int i;
-    for (i = 0; i < 8; i++)
-        s->h[i] = iv[i];
-}
-
-static void SHA512_Block(SHA512_State *s, uint64_t *block) {
-    uint64_t w[80];
-    uint64_t a,b,c,d,e,f,g,h;
-    static const uint64_t k[] = {
-        INIT(0x428a2f98, 0xd728ae22), INIT(0x71374491, 0x23ef65cd),
-        INIT(0xb5c0fbcf, 0xec4d3b2f), INIT(0xe9b5dba5, 0x8189dbbc),
-        INIT(0x3956c25b, 0xf348b538), INIT(0x59f111f1, 0xb605d019),
-        INIT(0x923f82a4, 0xaf194f9b), INIT(0xab1c5ed5, 0xda6d8118),
-        INIT(0xd807aa98, 0xa3030242), INIT(0x12835b01, 0x45706fbe),
-        INIT(0x243185be, 0x4ee4b28c), INIT(0x550c7dc3, 0xd5ffb4e2),
-        INIT(0x72be5d74, 0xf27b896f), INIT(0x80deb1fe, 0x3b1696b1),
-        INIT(0x9bdc06a7, 0x25c71235), INIT(0xc19bf174, 0xcf692694),
-        INIT(0xe49b69c1, 0x9ef14ad2), INIT(0xefbe4786, 0x384f25e3),
-        INIT(0x0fc19dc6, 0x8b8cd5b5), INIT(0x240ca1cc, 0x77ac9c65),
-        INIT(0x2de92c6f, 0x592b0275), INIT(0x4a7484aa, 0x6ea6e483),
-        INIT(0x5cb0a9dc, 0xbd41fbd4), INIT(0x76f988da, 0x831153b5),
-        INIT(0x983e5152, 0xee66dfab), INIT(0xa831c66d, 0x2db43210),
-        INIT(0xb00327c8, 0x98fb213f), INIT(0xbf597fc7, 0xbeef0ee4),
-        INIT(0xc6e00bf3, 0x3da88fc2), INIT(0xd5a79147, 0x930aa725),
-        INIT(0x06ca6351, 0xe003826f), INIT(0x14292967, 0x0a0e6e70),
-        INIT(0x27b70a85, 0x46d22ffc), INIT(0x2e1b2138, 0x5c26c926),
-        INIT(0x4d2c6dfc, 0x5ac42aed), INIT(0x53380d13, 0x9d95b3df),
-        INIT(0x650a7354, 0x8baf63de), INIT(0x766a0abb, 0x3c77b2a8),
-        INIT(0x81c2c92e, 0x47edaee6), INIT(0x92722c85, 0x1482353b),
-        INIT(0xa2bfe8a1, 0x4cf10364), INIT(0xa81a664b, 0xbc423001),
-        INIT(0xc24b8b70, 0xd0f89791), INIT(0xc76c51a3, 0x0654be30),
-        INIT(0xd192e819, 0xd6ef5218), INIT(0xd6990624, 0x5565a910),
-        INIT(0xf40e3585, 0x5771202a), INIT(0x106aa070, 0x32bbd1b8),
-        INIT(0x19a4c116, 0xb8d2d0c8), INIT(0x1e376c08, 0x5141ab53),
-        INIT(0x2748774c, 0xdf8eeb99), INIT(0x34b0bcb5, 0xe19b48a8),
-        INIT(0x391c0cb3, 0xc5c95a63), INIT(0x4ed8aa4a, 0xe3418acb),
-        INIT(0x5b9cca4f, 0x7763e373), INIT(0x682e6ff3, 0xd6b2b8a3),
-        INIT(0x748f82ee, 0x5defb2fc), INIT(0x78a5636f, 0x43172f60),
-        INIT(0x84c87814, 0xa1f0ab72), INIT(0x8cc70208, 0x1a6439ec),
-        INIT(0x90befffa, 0x23631e28), INIT(0xa4506ceb, 0xde82bde9),
-        INIT(0xbef9a3f7, 0xb2c67915), INIT(0xc67178f2, 0xe372532b),
-        INIT(0xca273ece, 0xea26619c), INIT(0xd186b8c7, 0x21c0c207),
-        INIT(0xeada7dd6, 0xcde0eb1e), INIT(0xf57d4f7f, 0xee6ed178),
-        INIT(0x06f067aa, 0x72176fba), INIT(0x0a637dc5, 0xa2c898a6),
-        INIT(0x113f9804, 0xbef90dae), INIT(0x1b710b35, 0x131c471b),
-        INIT(0x28db77f5, 0x23047d84), INIT(0x32caab7b, 0x40c72493),
-        INIT(0x3c9ebe0a, 0x15c9bebc), INIT(0x431d67c4, 0x9c100d4c),
-        INIT(0x4cc5d4be, 0xcb3e42b6), INIT(0x597f299c, 0xfc657e2a),
-        INIT(0x5fcb6fab, 0x3ad6faec), INIT(0x6c44198c, 0x4a475817),
-    };
+static const uint64_t sha512_initial_state[] = {
+    0x6a09e667f3bcc908ULL,
+    0xbb67ae8584caa73bULL,
+    0x3c6ef372fe94f82bULL,
+    0xa54ff53a5f1d36f1ULL,
+    0x510e527fade682d1ULL,
+    0x9b05688c2b3e6c1fULL,
+    0x1f83d9abfb41bd6bULL,
+    0x5be0cd19137e2179ULL,
+};
 
-    int t;
+static const uint64_t sha384_initial_state[] = {
+    0xcbbb9d5dc1059ed8ULL,
+    0x629a292a367cd507ULL,
+    0x9159015a3070dd17ULL,
+    0x152fecd8f70e5939ULL,
+    0x67332667ffc00b31ULL,
+    0x8eb44a8768581511ULL,
+    0xdb0c2e0d64f98fa7ULL,
+    0x47b5481dbefa4fa4ULL,
+};
 
-    for (t = 0; t < 16; t++)
-        w[t] = block[t];
-
-    for (t = 16; t < 80; t++) {
-        uint64_t p, q, r, tmp;
-        smallsigma1(p, tmp, w[t-2]);
-        smallsigma0(q, tmp, w[t-15]);
-        add(r, p, q);
-        add(p, r, w[t-7]);
-        add(w[t], p, w[t-16]);
-    }
+static const uint64_t sha512_round_constants[] = {
+    0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL,
+    0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL,
+    0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL,
+    0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL,
+    0xd807aa98a3030242ULL, 0x12835b0145706fbeULL,
+    0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL,
+    0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL,
+    0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL,
+    0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL,
+    0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL,
+    0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL,
+    0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL,
+    0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL,
+    0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL,
+    0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL,
+    0x06ca6351e003826fULL, 0x142929670a0e6e70ULL,
+    0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL,
+    0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,
+    0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL,
+    0x81c2c92e47edaee6ULL, 0x92722c851482353bULL,
+    0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL,
+    0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL,
+    0xd192e819d6ef5218ULL, 0xd69906245565a910ULL,
+    0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL,
+    0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL,
+    0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL,
+    0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL,
+    0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL,
+    0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL,
+    0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL,
+    0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL,
+    0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL,
+    0xca273eceea26619cULL, 0xd186b8c721c0c207ULL,
+    0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL,
+    0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL,
+    0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
+    0x28db77f523047d84ULL, 0x32caab7b40c72493ULL,
+    0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL,
+    0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL,
+    0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL,
+};
 
-    a = s->h[0]; b = s->h[1]; c = s->h[2]; d = s->h[3];
-    e = s->h[4]; f = s->h[5]; g = s->h[6]; h = s->h[7];
-
-    for (t = 0; t < 80; t+=8) {
-        uint64_t tmp, p, q, r;
-
-#define ROUND(j,a,b,c,d,e,f,g,h) \
-        bigsigma1(p, tmp, e); \
-        Ch(q, tmp, e, f, g); \
-        add(r, p, q); \
-        add(p, r, k[j]) ; \
-        add(q, p, w[j]); \
-        add(r, q, h); \
-        bigsigma0(p, tmp, a); \
-        Maj(tmp, q, a, b, c); \
-        add(q, tmp, p); \
-        add(p, r, d); \
-        d = p; \
-        add(h, q, r);
-
-        ROUND(t+0, a,b,c,d,e,f,g,h);
-        ROUND(t+1, h,a,b,c,d,e,f,g);
-        ROUND(t+2, g,h,a,b,c,d,e,f);
-        ROUND(t+3, f,g,h,a,b,c,d,e);
-        ROUND(t+4, e,f,g,h,a,b,c,d);
-        ROUND(t+5, d,e,f,g,h,a,b,c);
-        ROUND(t+6, c,d,e,f,g,h,a,b);
-        ROUND(t+7, b,c,d,e,f,g,h,a);
-    }
+#define SHA512_ROUNDS 80
+
+typedef struct sha512_block sha512_block;
+struct sha512_block {
+    uint8_t block[128];
+    size_t used;
+    uint64_t lenhi, lenlo;
+};
+
+static inline void sha512_block_setup(sha512_block *blk)
+{
+    blk->used = 0;
+    blk->lenhi = blk->lenlo = 0;
+}
+
+static inline bool sha512_block_write(
+    sha512_block *blk, const void **vdata, size_t *len)
+{
+    size_t blkleft = sizeof(blk->block) - blk->used;
+    size_t chunk = *len < blkleft ? *len : blkleft;
+
+    const uint8_t *p = *vdata;
+    memcpy(blk->block + blk->used, p, chunk);
+    *vdata = p + chunk;
+    *len -= chunk;
+    blk->used += chunk;
 
-    {
-        uint64_t tmp;
-#define UPDATE(state, local) ( tmp = state, add(state, tmp, local) )
-        UPDATE(s->h[0], a); UPDATE(s->h[1], b);
-        UPDATE(s->h[2], c); UPDATE(s->h[3], d);
-        UPDATE(s->h[4], e); UPDATE(s->h[5], f);
-        UPDATE(s->h[6], g); UPDATE(s->h[7], h);
+    size_t chunkbits = chunk << 3;
+
+    blk->lenlo += chunkbits;
+    blk->lenhi += (blk->lenlo < chunkbits);
+
+    if (blk->used == sizeof(blk->block)) {
+        blk->used = 0;
+        return true;
     }
+
+    return false;
+}
+
+static inline void sha512_block_pad(sha512_block *blk, BinarySink *bs)
+{
+    uint64_t final_lenhi = blk->lenhi;
+    uint64_t final_lenlo = blk->lenlo;
+    size_t pad = 127 & (111 - blk->used);
+
+    put_byte(bs, 0x80);
+    put_padding(bs, pad, 0);
+    put_uint64(bs, final_lenhi);
+    put_uint64(bs, final_lenlo);
+
+    assert(blk->used == 0 && "Should have exactly hit a block boundary");
 }
 
 /* ----------------------------------------------------------------------
- * Outer SHA512 algorithm: take an arbitrary length byte string,
- * convert it into 16-doubleword blocks with the prescribed padding
- * at the end, and pass those blocks to the core SHA512 algorithm.
+ * Software implementation of SHA-512.
  */
 
-static void SHA512_BinarySink_write(BinarySink *bs,
-                                    const void *p, size_t len);
+static inline uint64_t ror(uint64_t x, unsigned y)
+{
+    return (x << (63 & -y)) | (x >> (63 & y));
+}
 
-void SHA512_Init(SHA512_State *s) {
-    SHA512_Core_Init(s);
-    s->blkused = 0;
-    s->lenhi = s->lenlo = 0;
-    BinarySink_INIT(s, SHA512_BinarySink_write);
+static inline uint64_t Ch(uint64_t ctrl, uint64_t if1, uint64_t if0)
+{
+    return if0 ^ (ctrl & (if1 ^ if0));
 }
 
-void SHA384_Init(SHA512_State *s) {
-    SHA384_Core_Init(s);
-    s->blkused = 0;
-    s->lenhi = s->lenlo = 0;
-    BinarySink_INIT(s, SHA512_BinarySink_write);
+static inline uint64_t Maj(uint64_t x, uint64_t y, uint64_t z)
+{
+    return (x & y) | (z & (x | y));
 }
 
-static void SHA512_BinarySink_write(BinarySink *bs,
-                                    const void *p, size_t len)
+static inline uint64_t Sigma_0(uint64_t x)
 {
-    SHA512_State *s = BinarySink_DOWNCAST(bs, SHA512_State);
-    unsigned char *q = (unsigned char *)p;
-    uint64_t wordblock[16];
-    int i;
+    return ror(x,28) ^ ror(x,34) ^ ror(x,39);
+}
 
-    /*
-     * Update the length field.
-     */
-    s->lenlo += len;
-    s->lenhi += (s->lenlo < len);
-
-    if (s->blkused && s->blkused+len < BLKSIZE) {
-        /*
-         * Trivial case: just add to the block.
-         */
-        memcpy(s->block + s->blkused, q, len);
-        s->blkused += len;
-    } else {
-        /*
-         * We must complete and process at least one block.
-         */
-        while (s->blkused + len >= BLKSIZE) {
-            memcpy(s->block + s->blkused, q, BLKSIZE - s->blkused);
-            q += BLKSIZE - s->blkused;
-            len -= BLKSIZE - s->blkused;
-            /* Now process the block. Gather bytes big-endian into words */
-            for (i = 0; i < 16; i++)
-                wordblock[i] = GET_64BIT_MSB_FIRST(s->block + i*8);
-            SHA512_Block(s, wordblock);
-            s->blkused = 0;
-        }
-        memcpy(s->block, q, len);
-        s->blkused = len;
+static inline uint64_t Sigma_1(uint64_t x)
+{
+    return ror(x,14) ^ ror(x,18) ^ ror(x,41);
+}
+
+static inline uint64_t sigma_0(uint64_t x)
+{
+    return ror(x,1) ^ ror(x,8) ^ (x >> 7);
+}
+
+static inline uint64_t sigma_1(uint64_t x)
+{
+    return ror(x,19) ^ ror(x,61) ^ (x >> 6);
+}
+
+static inline void sha512_sw_round(
+    unsigned round_index, const uint64_t *schedule,
+    uint64_t *a, uint64_t *b, uint64_t *c, uint64_t *d,
+    uint64_t *e, uint64_t *f, uint64_t *g, uint64_t *h)
+{
+    uint64_t t1 = *h + Sigma_1(*e) + Ch(*e,*f,*g) +
+        sha512_round_constants[round_index] + schedule[round_index];
+
+    uint64_t t2 = Sigma_0(*a) + Maj(*a,*b,*c);
+
+    *d += t1;
+    *h = t1 + t2;
+}
+
+static void sha512_sw_block(uint64_t *core, const uint8_t *block)
+{
+    uint64_t w[SHA512_ROUNDS];
+    uint64_t a,b,c,d,e,f,g,h;
+
+    int t;
+
+    for (t = 0; t < 16; t++)
+        w[t] = GET_64BIT_MSB_FIRST(block + 8*t);
+
+    for (t = 16; t < SHA512_ROUNDS; t++)
+        w[t] = w[t-16] + w[t-7] + sigma_0(w[t-15]) + sigma_1(w[t-2]);
+
+    a = core[0]; b = core[1]; c = core[2]; d = core[3];
+    e = core[4]; f = core[5]; g = core[6]; h = core[7];
+
+    for (t = 0; t < SHA512_ROUNDS; t+=8) {
+        sha512_sw_round(t+0, w, &a,&b,&c,&d,&e,&f,&g,&h);
+        sha512_sw_round(t+1, w, &h,&a,&b,&c,&d,&e,&f,&g);
+        sha512_sw_round(t+2, w, &g,&h,&a,&b,&c,&d,&e,&f);
+        sha512_sw_round(t+3, w, &f,&g,&h,&a,&b,&c,&d,&e);
+        sha512_sw_round(t+4, w, &e,&f,&g,&h,&a,&b,&c,&d);
+        sha512_sw_round(t+5, w, &d,&e,&f,&g,&h,&a,&b,&c);
+        sha512_sw_round(t+6, w, &c,&d,&e,&f,&g,&h,&a,&b);
+        sha512_sw_round(t+7, w, &b,&c,&d,&e,&f,&g,&h,&a);
     }
+
+    core[0] += a; core[1] += b; core[2] += c; core[3] += d;
+    core[4] += e; core[5] += f; core[6] += g; core[7] += h;
+
+    smemclr(w, sizeof(w));
 }
 
-void SHA512_Final(SHA512_State *s, unsigned char *digest) {
-    int i;
-    int pad;
-    unsigned char c[BLKSIZE];
-    uint64_t lenhi, lenlo;
+typedef struct sha512_sw {
+    uint64_t core[8];
+    sha512_block blk;
+    BinarySink_IMPLEMENTATION;
+    ssh_hash hash;
+} sha512_sw;
+
+static void sha512_sw_write(BinarySink *bs, const void *vp, size_t len);
+
+static ssh_hash *sha512_sw_new(const ssh_hashalg *alg)
+{
+    sha512_sw *s = snew(sha512_sw);
 
-    if (s->blkused >= BLKSIZE-16)
-        pad = (BLKSIZE-16) + BLKSIZE - s->blkused;
-    else
-        pad = (BLKSIZE-16) - s->blkused;
+    s->hash.vt = alg;
+    BinarySink_INIT(s, sha512_sw_write);
+    BinarySink_DELEGATE_INIT(&s->hash, s);
+    return &s->hash;
+}
 
-    lenhi = (s->lenhi << 3) | (s->lenlo >> (32-3));
-    lenlo = (s->lenlo << 3);
+static void sha512_sw_reset(ssh_hash *hash)
+{
+    sha512_sw *s = container_of(hash, sha512_sw, hash);
 
-    memset(c, 0, pad);
-    c[0] = 0x80;
-    put_data(s, &c, pad);
+    /* The 'extra' field in the ssh_hashalg indicates which
+     * initialisation vector we're using */
+    memcpy(s->core, hash->vt->extra, sizeof(s->core));
+    sha512_block_setup(&s->blk);
+}
 
-    put_uint64(s, lenhi);
-    put_uint64(s, lenlo);
+static void sha512_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+    sha512_sw *copy = container_of(hcopy, sha512_sw, hash);
+    sha512_sw *orig = container_of(horig, sha512_sw, hash);
 
-    for (i = 0; i < 8; i++)
-        PUT_64BIT_MSB_FIRST(digest + i*8, s->h[i]);
+    memcpy(copy, orig, sizeof(*copy));
+    BinarySink_COPIED(copy);
+    BinarySink_DELEGATE_INIT(&copy->hash, copy);
 }
 
-void SHA384_Final(SHA512_State *s, unsigned char *digest) {
-    unsigned char biggerDigest[512 / 8];
-    SHA512_Final(s, biggerDigest);
-    memcpy(digest, biggerDigest, 384 / 8);
+static void sha512_sw_free(ssh_hash *hash)
+{
+    sha512_sw *s = container_of(hash, sha512_sw, hash);
+
+    smemclr(s, sizeof(*s));
+    sfree(s);
 }
 
-void SHA512_Simple(const void *p, int len, unsigned char *output) {
-    SHA512_State s;
+static void sha512_sw_write(BinarySink *bs, const void *vp, size_t len)
+{
+    sha512_sw *s = BinarySink_DOWNCAST(bs, sha512_sw);
 
-    SHA512_Init(&s);
-    put_data(&s, p, len);
-    SHA512_Final(&s, output);
-    smemclr(&s, sizeof(s));
+    while (len > 0)
+        if (sha512_block_write(&s->blk, &vp, &len))
+            sha512_sw_block(s->core, s->blk.block);
 }
 
-void SHA384_Simple(const void *p, int len, unsigned char *output) {
-    SHA512_State s;
+static void sha512_sw_digest(ssh_hash *hash, uint8_t *digest)
+{
+    sha512_sw *s = container_of(hash, sha512_sw, hash);
 
-    SHA384_Init(&s);
-    put_data(&s, p, len);
-    SHA384_Final(&s, output);
-    smemclr(&s, sizeof(s));
+    sha512_block_pad(&s->blk, BinarySink_UPCAST(s));
+    for (size_t i = 0; i < hash->vt->hlen / 8; i++)
+        PUT_64BIT_MSB_FIRST(digest + 8*i, s->core[i]);
 }
 
+const ssh_hashalg ssh_sha512_sw = {
+    .new = sha512_sw_new,
+    .reset = sha512_sw_reset,
+    .copyfrom = sha512_sw_copyfrom,
+    .digest = sha512_sw_digest,
+    .free = sha512_sw_free,
+    .hlen = 64,
+    .blocklen = 128,
+    HASHALG_NAMES_ANNOTATED("SHA-512", "unaccelerated"),
+    .extra = sha512_initial_state,
+};
+
+const ssh_hashalg ssh_sha384_sw = {
+    .new = sha512_sw_new,
+    .reset = sha512_sw_reset,
+    .copyfrom = sha512_sw_copyfrom,
+    .digest = sha512_sw_digest,
+    .free = sha512_sw_free,
+    .hlen = 48,
+    .blocklen = 128,
+    HASHALG_NAMES_ANNOTATED("SHA-384", "unaccelerated"),
+    .extra = sha384_initial_state,
+};
+
+/* ----------------------------------------------------------------------
+ * Hardware-accelerated implementation of SHA-512 using Arm NEON.
+ */
+
+#if HW_SHA512 == HW_SHA512_NEON
+
+/*
+ * Manually set the target architecture, if we decided above that we
+ * need to.
+ */
+#ifdef USE_CLANG_ATTR_TARGET_AARCH64
 /*
- * Thin abstraction for things where hashes are pluggable.
+ * A spot of cheating: redefine some ACLE feature macros before
+ * including arm_neon.h. Otherwise we won't get the SHA intrinsics
+ * defined by that header, because it will be looking at the settings
+ * for the whole translation unit rather than the ones we're going to
+ * put on some particular functions using __attribute__((target)).
  */
+#define __ARM_NEON 1
+#define __ARM_FEATURE_CRYPTO 1
+#define FUNC_ISA __attribute__ ((target("neon,sha3")))
+#endif /* USE_CLANG_ATTR_TARGET_AARCH64 */
+
+#ifndef FUNC_ISA
+#define FUNC_ISA
+#endif
+
+#ifdef USE_ARM64_NEON_H
+#include <arm64_neon.h>
+#else
+#include <arm_neon.h>
+#endif
+
+static bool sha512_hw_available(void)
+{
+    /*
+     * For Arm, we delegate to a per-platform detection function (see
+     * explanation in sshaes.c).
+     */
+    return platform_sha512_hw_available();
+}
 
-struct sha512_hash {
-    SHA512_State state;
-    ssh_hash hash;
+#if defined __clang__
+/*
+ * As of 2020-12-24, I've found that clang doesn't provide the SHA-512
+ * NEON intrinsics. So I define my own set using inline assembler, and
+ * use #define to effectively rename them over the top of the standard
+ * names.
+ *
+ * The aim of that #define technique is that it should avoid a build
+ * failure if these intrinsics _are_ defined in <arm_neon.h>.
+ * Obviously it would be better in that situation to switch back to
+ * using the real intrinsics, but until I see a version of clang that
+ * supports them, I won't know what version number to test in the
+ * ifdef.
+ */
+static inline FUNC_ISA
+uint64x2_t vsha512su0q_u64_asm(uint64x2_t x, uint64x2_t y) {
+    __asm__("sha512su0 %0.2D,%1.2D" : "+w" (x) : "w" (y));
+    return x;
+}
+static inline FUNC_ISA
+uint64x2_t vsha512su1q_u64_asm(uint64x2_t x, uint64x2_t y, uint64x2_t z) {
+    __asm__("sha512su1 %0.2D,%1.2D,%2.2D" : "+w" (x) : "w" (y), "w" (z));
+    return x;
+}
+static inline FUNC_ISA
+uint64x2_t vsha512hq_u64_asm(uint64x2_t x, uint64x2_t y, uint64x2_t z) {
+    __asm__("sha512h %0,%1,%2.2D" : "+w" (x) : "w" (y), "w" (z));
+    return x;
+}
+static inline FUNC_ISA
+uint64x2_t vsha512h2q_u64_asm(uint64x2_t x, uint64x2_t y, uint64x2_t z) {
+    __asm__("sha512h2 %0,%1,%2.2D" : "+w" (x) : "w" (y), "w" (z));
+    return x;
+}
+#undef vsha512su0q_u64
+#define vsha512su0q_u64 vsha512su0q_u64_asm
+#undef vsha512su1q_u64
+#define vsha512su1q_u64 vsha512su1q_u64_asm
+#undef vsha512hq_u64
+#define vsha512hq_u64 vsha512hq_u64_asm
+#undef vsha512h2q_u64
+#define vsha512h2q_u64 vsha512h2q_u64_asm
+#endif /* defined __clang__ */
+
+typedef struct sha512_neon_core sha512_neon_core;
+struct sha512_neon_core {
+    uint64x2_t ab, cd, ef, gh;
 };
 
-static ssh_hash *sha512_new(const ssh_hashalg *alg)
+FUNC_ISA
+static inline uint64x2_t sha512_neon_load_input(const uint8_t *p)
+{
+    return vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p)));
+}
+
+FUNC_ISA
+static inline uint64x2_t sha512_neon_schedule_update(
+    uint64x2_t m8, uint64x2_t m7, uint64x2_t m4, uint64x2_t m3, uint64x2_t m1)
+{
+    /*
+     * vsha512su0q_u64() takes words from a long way back in the
+     * schedule and performs the sigma_0 half of the computation of
+     * the next two 64-bit message-schedule words.
+     *
+     * vsha512su1q_u64() combines the result of that with the sigma_1
+     * steps, to output the finished version of those two words. The
+     * total amount of input data it requires fits nicely into three
+     * 128-bit vector registers, but one of those registers is
+     * misaligned compared to the 128-bit chunks that the message
+     * schedule is stored in. So we use vextq_u64 to make one of its
+     * input words out of the second half of m4 and the first half of
+     * m3.
+     */
+    return vsha512su1q_u64(vsha512su0q_u64(m8, m7), m1, vextq_u64(m4, m3, 1));
+}
+
+FUNC_ISA
+static inline void sha512_neon_round2(
+    unsigned round_index, uint64x2_t schedule_words,
+    uint64x2_t *ab, uint64x2_t *cd, uint64x2_t *ef, uint64x2_t *gh)
+{
+    /*
+     * vsha512hq_u64 performs the Sigma_1 and Ch half of the
+     * computation of two rounds of SHA-512 (including feeding back
+     * one of the outputs from the first of those half-rounds into the
+     * second one).
+     *
+     * vsha512h2q_u64 combines the result of that with the Sigma_0 and
+     * Maj steps, and outputs one 128-bit vector that replaces the gh
+     * piece of the input hash state, and a second that updates cd by
+     * addition.
+     *
+     * Similarly to vsha512su1q_u64 above, some of the input registers
+     * expected by these instructions are misaligned by 64 bits
+     * relative to the chunks we've divided the hash state into, so we
+     * have to start by making 'de' and 'fg' words out of our input
+     * cd,ef,gh, using vextq_u64.
+     *
+     * Also, one of the inputs to vsha512hq_u64 is expected to contain
+     * the results of summing gh + two round constants + two words of
+     * message schedule, but the two words of the message schedule
+     * have to be the opposite way round in the vector register from
+     * the way that vsha512su1q_u64 output them. Hence, there's
+     * another vextq_u64 in here that swaps the two halves of the
+     * initial_sum vector register.
+     *
+     * (This also means that I don't have to prepare a specially
+     * reordered version of the sha512_round_constants[] array: as
+     * long as I'm unavoidably doing a swap at run time _anyway_, I
+     * can load from the normally ordered version of that array, and
+     * just take care to fold in that data _before_ the swap rather
+     * than after.)
+     */
+
+    /* Load two round constants, with the first one in the low half */
+    uint64x2_t round_constants = vld1q_u64(
+        sha512_round_constants + round_index);
+
+    /* Add schedule words to round constants */
+    uint64x2_t initial_sum = vaddq_u64(schedule_words, round_constants);
+
+    /* Swap that sum around so the word used in the first of the two
+     * rounds is in the _high_ half of the vector, matching where h
+     * lives in the gh vector */
+    uint64x2_t swapped_initial_sum = vextq_u64(initial_sum, initial_sum, 1);
+
+    /* Add gh to that, now that they're matching ways round */
+    uint64x2_t sum = vaddq_u64(swapped_initial_sum, *gh);
+
+    /* Make the misaligned de and fg words */
+    uint64x2_t de = vextq_u64(*cd, *ef, 1);
+    uint64x2_t fg = vextq_u64(*ef, *gh, 1);
+
+    /* Now we're ready to put all the pieces together. The output from
+     * vsha512h2q_u64 can be used directly as the new gh, and the
+     * output from vsha512hq_u64 is simultaneously the intermediate
+     * value passed to h2 and the thing you have to add on to cd. */
+    uint64x2_t intermed = vsha512hq_u64(sum, fg, de);
+    *gh = vsha512h2q_u64(intermed, *cd, *ab);
+    *cd = vaddq_u64(*cd, intermed);
+}
+
+FUNC_ISA
+static inline void sha512_neon_block(sha512_neon_core *core, const uint8_t *p)
 {
-    struct sha512_hash *h = snew(struct sha512_hash);
-    SHA512_Init(&h->state);
-    h->hash.vt = alg;
-    BinarySink_DELEGATE_INIT(&h->hash, &h->state);
-    return &h->hash;
+    uint64x2_t s0, s1, s2, s3, s4, s5, s6, s7;
+
+    uint64x2_t ab = core->ab, cd = core->cd, ef = core->ef, gh = core->gh;
+
+    s0 = sha512_neon_load_input(p + 16*0);
+    sha512_neon_round2(0, s0, &ab, &cd, &ef, &gh);
+    s1 = sha512_neon_load_input(p + 16*1);
+    sha512_neon_round2(2, s1, &gh, &ab, &cd, &ef);
+    s2 = sha512_neon_load_input(p + 16*2);
+    sha512_neon_round2(4, s2, &ef, &gh, &ab, &cd);
+    s3 = sha512_neon_load_input(p + 16*3);
+    sha512_neon_round2(6, s3, &cd, &ef, &gh, &ab);
+    s4 = sha512_neon_load_input(p + 16*4);
+    sha512_neon_round2(8, s4, &ab, &cd, &ef, &gh);
+    s5 = sha512_neon_load_input(p + 16*5);
+    sha512_neon_round2(10, s5, &gh, &ab, &cd, &ef);
+    s6 = sha512_neon_load_input(p + 16*6);
+    sha512_neon_round2(12, s6, &ef, &gh, &ab, &cd);
+    s7 = sha512_neon_load_input(p + 16*7);
+    sha512_neon_round2(14, s7, &cd, &ef, &gh, &ab);
+    s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7);
+    sha512_neon_round2(16, s0, &ab, &cd, &ef, &gh);
+    s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0);
+    sha512_neon_round2(18, s1, &gh, &ab, &cd, &ef);
+    s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1);
+    sha512_neon_round2(20, s2, &ef, &gh, &ab, &cd);
+    s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2);
+    sha512_neon_round2(22, s3, &cd, &ef, &gh, &ab);
+    s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3);
+    sha512_neon_round2(24, s4, &ab, &cd, &ef, &gh);
+    s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4);
+    sha512_neon_round2(26, s5, &gh, &ab, &cd, &ef);
+    s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5);
+    sha512_neon_round2(28, s6, &ef, &gh, &ab, &cd);
+    s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6);
+    sha512_neon_round2(30, s7, &cd, &ef, &gh, &ab);
+    s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7);
+    sha512_neon_round2(32, s0, &ab, &cd, &ef, &gh);
+    s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0);
+    sha512_neon_round2(34, s1, &gh, &ab, &cd, &ef);
+    s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1);
+    sha512_neon_round2(36, s2, &ef, &gh, &ab, &cd);
+    s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2);
+    sha512_neon_round2(38, s3, &cd, &ef, &gh, &ab);
+    s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3);
+    sha512_neon_round2(40, s4, &ab, &cd, &ef, &gh);
+    s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4);
+    sha512_neon_round2(42, s5, &gh, &ab, &cd, &ef);
+    s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5);
+    sha512_neon_round2(44, s6, &ef, &gh, &ab, &cd);
+    s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6);
+    sha512_neon_round2(46, s7, &cd, &ef, &gh, &ab);
+    s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7);
+    sha512_neon_round2(48, s0, &ab, &cd, &ef, &gh);
+    s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0);
+    sha512_neon_round2(50, s1, &gh, &ab, &cd, &ef);
+    s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1);
+    sha512_neon_round2(52, s2, &ef, &gh, &ab, &cd);
+    s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2);
+    sha512_neon_round2(54, s3, &cd, &ef, &gh, &ab);
+    s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3);
+    sha512_neon_round2(56, s4, &ab, &cd, &ef, &gh);
+    s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4);
+    sha512_neon_round2(58, s5, &gh, &ab, &cd, &ef);
+    s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5);
+    sha512_neon_round2(60, s6, &ef, &gh, &ab, &cd);
+    s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6);
+    sha512_neon_round2(62, s7, &cd, &ef, &gh, &ab);
+    s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7);
+    sha512_neon_round2(64, s0, &ab, &cd, &ef, &gh);
+    s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0);
+    sha512_neon_round2(66, s1, &gh, &ab, &cd, &ef);
+    s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1);
+    sha512_neon_round2(68, s2, &ef, &gh, &ab, &cd);
+    s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2);
+    sha512_neon_round2(70, s3, &cd, &ef, &gh, &ab);
+    s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3);
+    sha512_neon_round2(72, s4, &ab, &cd, &ef, &gh);
+    s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4);
+    sha512_neon_round2(74, s5, &gh, &ab, &cd, &ef);
+    s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5);
+    sha512_neon_round2(76, s6, &ef, &gh, &ab, &cd);
+    s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6);
+    sha512_neon_round2(78, s7, &cd, &ef, &gh, &ab);
+
+    core->ab = vaddq_u64(core->ab, ab);
+    core->cd = vaddq_u64(core->cd, cd);
+    core->ef = vaddq_u64(core->ef, ef);
+    core->gh = vaddq_u64(core->gh, gh);
 }
 
-static ssh_hash *sha512_copy(ssh_hash *hashold)
+typedef struct sha512_neon {
+    sha512_neon_core core;
+    sha512_block blk;
+    BinarySink_IMPLEMENTATION;
+    ssh_hash hash;
+} sha512_neon;
+
+static void sha512_neon_write(BinarySink *bs, const void *vp, size_t len);
+
+static ssh_hash *sha512_neon_new(const ssh_hashalg *alg)
 {
-    struct sha512_hash *hold, *hnew;
-    ssh_hash *hashnew = sha512_new(hashold->vt);
+    if (!sha512_hw_available_cached())
+        return NULL;
+
+    sha512_neon *s = snew(sha512_neon);
 
-    hold = container_of(hashold, struct sha512_hash, hash);
-    hnew = container_of(hashnew, struct sha512_hash, hash);
+    s->hash.vt = alg;
+    BinarySink_INIT(s, sha512_neon_write);
+    BinarySink_DELEGATE_INIT(&s->hash, s);
+    return &s->hash;
+}
+
+static void sha512_neon_reset(ssh_hash *hash)
+{
+    sha512_neon *s = container_of(hash, sha512_neon, hash);
+    const uint64_t *iv = (const uint64_t *)hash->vt->extra;
 
-    hnew->state = hold->state;
-    BinarySink_COPIED(&hnew->state);
+    s->core.ab = vld1q_u64(iv);
+    s->core.cd = vld1q_u64(iv+2);
+    s->core.ef = vld1q_u64(iv+4);
+    s->core.gh = vld1q_u64(iv+6);
 
-    return hashnew;
+    sha512_block_setup(&s->blk);
 }
 
-static void sha512_free(ssh_hash *hash)
+static void sha512_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
 {
-    struct sha512_hash *h = container_of(hash, struct sha512_hash, hash);
+    sha512_neon *copy = container_of(hcopy, sha512_neon, hash);
+    sha512_neon *orig = container_of(horig, sha512_neon, hash);
 
-    smemclr(h, sizeof(*h));
-    sfree(h);
+    *copy = *orig; /* structure copy */
+
+    BinarySink_COPIED(copy);
+    BinarySink_DELEGATE_INIT(&copy->hash, copy);
 }
 
-static void sha512_final(ssh_hash *hash, unsigned char *output)
+static void sha512_neon_free(ssh_hash *hash)
 {
-    struct sha512_hash *h = container_of(hash, struct sha512_hash, hash);
-    SHA512_Final(&h->state, output);
-    sha512_free(hash);
+    sha512_neon *s = container_of(hash, sha512_neon, hash);
+    smemclr(s, sizeof(*s));
+    sfree(s);
 }
 
-const ssh_hashalg ssh_sha512 = {
-    sha512_new, sha512_copy, sha512_final, sha512_free,
-    64, BLKSIZE, HASHALG_NAMES_BARE("SHA-512"),
+static void sha512_neon_write(BinarySink *bs, const void *vp, size_t len)
+{
+    sha512_neon *s = BinarySink_DOWNCAST(bs, sha512_neon);
+
+    while (len > 0)
+        if (sha512_block_write(&s->blk, &vp, &len))
+            sha512_neon_block(&s->core, s->blk.block);
+}
+
+static void sha512_neon_digest(ssh_hash *hash, uint8_t *digest)
+{
+    sha512_neon *s = container_of(hash, sha512_neon, hash);
+
+    sha512_block_pad(&s->blk, BinarySink_UPCAST(s));
+
+    vst1q_u8(digest,    vrev64q_u8(vreinterpretq_u8_u64(s->core.ab)));
+    vst1q_u8(digest+16, vrev64q_u8(vreinterpretq_u8_u64(s->core.cd)));
+    vst1q_u8(digest+32, vrev64q_u8(vreinterpretq_u8_u64(s->core.ef)));
+    vst1q_u8(digest+48, vrev64q_u8(vreinterpretq_u8_u64(s->core.gh)));
+}
+
+static void sha384_neon_digest(ssh_hash *hash, uint8_t *digest)
+{
+    sha512_neon *s = container_of(hash, sha512_neon, hash);
+
+    sha512_block_pad(&s->blk, BinarySink_UPCAST(s));
+
+    vst1q_u8(digest,    vrev64q_u8(vreinterpretq_u8_u64(s->core.ab)));
+    vst1q_u8(digest+16, vrev64q_u8(vreinterpretq_u8_u64(s->core.cd)));
+    vst1q_u8(digest+32, vrev64q_u8(vreinterpretq_u8_u64(s->core.ef)));
+}
+
+const ssh_hashalg ssh_sha512_hw = {
+    .new = sha512_neon_new,
+    .reset = sha512_neon_reset,
+    .copyfrom = sha512_neon_copyfrom,
+    .digest = sha512_neon_digest,
+    .free = sha512_neon_free,
+    .hlen = 64,
+    .blocklen = 128,
+    HASHALG_NAMES_ANNOTATED("SHA-512", "NEON accelerated"),
+    .extra = sha512_initial_state,
 };
 
-static ssh_hash *sha384_new(const ssh_hashalg *alg)
+const ssh_hashalg ssh_sha384_hw = {
+    .new = sha512_neon_new,
+    .reset = sha512_neon_reset,
+    .copyfrom = sha512_neon_copyfrom,
+    .digest = sha384_neon_digest,
+    .free = sha512_neon_free,
+    .hlen = 48,
+    .blocklen = 128,
+    HASHALG_NAMES_ANNOTATED("SHA-384", "NEON accelerated"),
+    .extra = sha384_initial_state,
+};
+
+/* ----------------------------------------------------------------------
+ * Stub functions if we have no hardware-accelerated SHA-512. In this
+ * case, sha512_hw_new returns NULL (though it should also never be
+ * selected by sha512_select, so the only thing that should even be
+ * _able_ to call it is testcrypt). As a result, the remaining vtable
+ * functions should never be called at all.
+ */
+
+#elif HW_SHA512 == HW_SHA512_NONE
+
+static bool sha512_hw_available(void)
 {
-    struct sha512_hash *h = snew(struct sha512_hash);
-    SHA384_Init(&h->state);
-    h->hash.vt = alg;
-    BinarySink_DELEGATE_INIT(&h->hash, &h->state);
-    return &h->hash;
+    return false;
 }
 
-static void sha384_final(ssh_hash *hash, unsigned char *output)
+static ssh_hash *sha512_stub_new(const ssh_hashalg *alg)
 {
-    struct sha512_hash *h = container_of(hash, struct sha512_hash, hash);
-    SHA384_Final(&h->state, output);
-    sha512_free(hash);
+    return NULL;
 }
 
-const ssh_hashalg ssh_sha384 = {
-    sha384_new, sha512_copy, sha384_final, sha512_free,
-    48, BLKSIZE, HASHALG_NAMES_BARE("SHA-384"),
+#define STUB_BODY { unreachable("Should never be called"); }
+
+static void sha512_stub_reset(ssh_hash *hash) STUB_BODY
+static void sha512_stub_copyfrom(ssh_hash *hash, ssh_hash *orig) STUB_BODY
+static void sha512_stub_free(ssh_hash *hash) STUB_BODY
+static void sha512_stub_digest(ssh_hash *hash, uint8_t *digest) STUB_BODY
+
+const ssh_hashalg ssh_sha512_hw = {
+    .new = sha512_stub_new,
+    .reset = sha512_stub_reset,
+    .copyfrom = sha512_stub_copyfrom,
+    .digest = sha512_stub_digest,
+    .free = sha512_stub_free,
+    .hlen = 64,
+    .blocklen = 128,
+    HASHALG_NAMES_ANNOTATED("SHA-512", "!NONEXISTENT ACCELERATED VERSION!"),
 };
+
+const ssh_hashalg ssh_sha384_hw = {
+    .new = sha512_stub_new,
+    .reset = sha512_stub_reset,
+    .copyfrom = sha512_stub_copyfrom,
+    .digest = sha512_stub_digest,
+    .free = sha512_stub_free,
+    .hlen = 48,
+    .blocklen = 128,
+    HASHALG_NAMES_ANNOTATED("SHA-384", "!NONEXISTENT ACCELERATED VERSION!"),
+};
+
+#endif /* HW_SHA512 */

+ 81 - 51
source/putty/sshsha.c

@@ -98,8 +98,10 @@ static ssh_hash *sha1_select(const ssh_hashalg *alg)
 }
 
 const ssh_hashalg ssh_sha1 = {
-    sha1_select, NULL, NULL, NULL,
-    20, 64, HASHALG_NAMES_ANNOTATED("SHA-1", "dummy selector vtable"),
+    .new = sha1_select,
+    .hlen = 20,
+    .blocklen = 64,
+    HASHALG_NAMES_ANNOTATED("SHA-1", "dummy selector vtable"),
 };
 
 /* ----------------------------------------------------------------------
@@ -266,26 +268,28 @@ static ssh_hash *sha1_sw_new(const ssh_hashalg *alg)
 {
     sha1_sw *s = snew(sha1_sw);
 
-    memcpy(s->core, sha1_initial_state, sizeof(s->core));
-
-    sha1_block_setup(&s->blk);
-
     s->hash.vt = alg;
     BinarySink_INIT(s, sha1_sw_write);
     BinarySink_DELEGATE_INIT(&s->hash, s);
     return &s->hash;
 }
 
-static ssh_hash *sha1_sw_copy(ssh_hash *hash)
+static void sha1_sw_reset(ssh_hash *hash)
 {
     sha1_sw *s = container_of(hash, sha1_sw, hash);
-    sha1_sw *copy = snew(sha1_sw);
 
-    memcpy(copy, s, sizeof(*copy));
+    memcpy(s->core, sha1_initial_state, sizeof(s->core));
+    sha1_block_setup(&s->blk);
+}
+
+static void sha1_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+    sha1_sw *copy = container_of(hcopy, sha1_sw, hash);
+    sha1_sw *orig = container_of(horig, sha1_sw, hash);
+
+    memcpy(copy, orig, sizeof(*copy));
     BinarySink_COPIED(copy);
     BinarySink_DELEGATE_INIT(&copy->hash, copy);
-
-    return &copy->hash;
 }
 
 static void sha1_sw_free(ssh_hash *hash)
@@ -305,7 +309,7 @@ static void sha1_sw_write(BinarySink *bs, const void *vp, size_t len)
             sha1_sw_block(s->core, s->blk.block);
 }
 
-static void sha1_sw_final(ssh_hash *hash, uint8_t *digest)
+static void sha1_sw_digest(ssh_hash *hash, uint8_t *digest)
 {
     sha1_sw *s = container_of(hash, sha1_sw, hash);
 
@@ -314,13 +318,18 @@ static void sha1_sw_final(ssh_hash *hash, uint8_t *digest)
     size_t i; // WINSCP
     for (i = 0; i < 5; i++)
         PUT_32BIT_MSB_FIRST(digest + 4*i, s->core[i]);
-    sha1_sw_free(hash);
     } // WINSCP
 }
 
 const ssh_hashalg ssh_sha1_sw = {
-    sha1_sw_new, sha1_sw_copy, sha1_sw_final, sha1_sw_free,
-    20, 64, HASHALG_NAMES_BARE("SHA-1"), // WINSCP (removed "unaccelerated" annotation)
+    .new = sha1_sw_new,
+    .reset = sha1_sw_reset,
+    .copyfrom = sha1_sw_copyfrom,
+    .digest = sha1_sw_digest,
+    .free = sha1_sw_free,
+    .hlen = 20,
+    .blocklen = 64,
+    HASHALG_NAMES_BARE("SHA-1"), // WINSCP (removed "unaccelerated" annotation)
 };
 
 /* ----------------------------------------------------------------------
@@ -583,39 +592,42 @@ static sha1_ni *sha1_ni_alloc(void)
     return s;
 }
 
-FUNC_ISA static ssh_hash *sha1_ni_new(const ssh_hashalg *alg)
+static ssh_hash *sha1_ni_new(const ssh_hashalg *alg)
 {
     if (!sha1_hw_available_cached())
         return NULL;
 
     sha1_ni *s = sha1_ni_alloc();
 
+    s->hash.vt = alg;
+    BinarySink_INIT(s, sha1_ni_write);
+    BinarySink_DELEGATE_INIT(&s->hash, s);
+    return &s->hash;
+}
+
+FUNC_ISA static void sha1_ni_reset(ssh_hash *hash)
+{
+    sha1_ni *s = container_of(hash, sha1_ni, hash);
+
     /* Initialise the core vectors in their storage order */
     s->core[0] = _mm_set_epi64x(
         0x67452301efcdab89ULL, 0x98badcfe10325476ULL);
     s->core[1] = _mm_set_epi32(0xc3d2e1f0, 0, 0, 0);
 
     sha1_block_setup(&s->blk);
-
-    s->hash.vt = alg;
-    BinarySink_INIT(s, sha1_ni_write);
-    BinarySink_DELEGATE_INIT(&s->hash, s);
-    return &s->hash;
 }
 
-static ssh_hash *sha1_ni_copy(ssh_hash *hash)
+static void sha1_ni_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
 {
-    sha1_ni *s = container_of(hash, sha1_ni, hash);
-    sha1_ni *copy = sha1_ni_alloc();
+    sha1_ni *copy = container_of(hcopy, sha1_ni, hash);
+    sha1_ni *orig = container_of(horig, sha1_ni, hash);
 
     void *ptf_save = copy->pointer_to_free;
-    *copy = *s; /* structure copy */
+    *copy = *orig; /* structure copy */
     copy->pointer_to_free = ptf_save;
 
     BinarySink_COPIED(copy);
     BinarySink_DELEGATE_INIT(&copy->hash, copy);
-
-    return &copy->hash;
 }
 
 static void sha1_ni_free(ssh_hash *hash)
@@ -636,7 +648,7 @@ static void sha1_ni_write(BinarySink *bs, const void *vp, size_t len)
             sha1_ni_block(s->core, s->blk.block);
 }
 
-FUNC_ISA static void sha1_ni_final(ssh_hash *hash, uint8_t *digest)
+FUNC_ISA static void sha1_ni_digest(ssh_hash *hash, uint8_t *digest)
 {
     sha1_ni *s = container_of(hash, sha1_ni, hash);
 
@@ -655,13 +667,17 @@ FUNC_ISA static void sha1_ni_final(ssh_hash *hash, uint8_t *digest)
     /* Finally, store the leftover word */
     uint32_t e = _mm_extract_epi32(s->core[1], 3);
     PUT_32BIT_MSB_FIRST(digest + 16, e);
-
-    sha1_ni_free(hash);
 }
 
 const ssh_hashalg ssh_sha1_hw = {
-    sha1_ni_new, sha1_ni_copy, sha1_ni_final, sha1_ni_free,
-    20, 64, HASHALG_NAMES_ANNOTATED("SHA-1", "SHA-NI accelerated"),
+    .new = sha1_ni_new,
+    .reset = sha1_ni_reset,
+    .copyfrom = sha1_ni_copyfrom,
+    .digest = sha1_ni_digest,
+    .free = sha1_ni_free,
+    .hlen = 20,
+    .blocklen = 64,
+    HASHALG_NAMES_ANNOTATED("SHA-1", "SHA-NI accelerated"),
 };
 
 /* ----------------------------------------------------------------------
@@ -823,28 +839,31 @@ static ssh_hash *sha1_neon_new(const ssh_hashalg *alg)
 
     sha1_neon *s = snew(sha1_neon);
 
-    s->core.abcd = vld1q_u32(sha1_initial_state);
-    s->core.e = sha1_initial_state[4];
-
-    sha1_block_setup(&s->blk);
-
     s->hash.vt = alg;
     BinarySink_INIT(s, sha1_neon_write);
     BinarySink_DELEGATE_INIT(&s->hash, s);
     return &s->hash;
 }
 
-static ssh_hash *sha1_neon_copy(ssh_hash *hash)
+static void sha1_neon_reset(ssh_hash *hash)
 {
     sha1_neon *s = container_of(hash, sha1_neon, hash);
-    sha1_neon *copy = snew(sha1_neon);
 
-    *copy = *s; /* structure copy */
+    s->core.abcd = vld1q_u32(sha1_initial_state);
+    s->core.e = sha1_initial_state[4];
+
+    sha1_block_setup(&s->blk);
+}
+
+static void sha1_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+    sha1_neon *copy = container_of(hcopy, sha1_neon, hash);
+    sha1_neon *orig = container_of(horig, sha1_neon, hash);
+
+    *copy = *orig; /* structure copy */
 
     BinarySink_COPIED(copy);
     BinarySink_DELEGATE_INIT(&copy->hash, copy);
-
-    return &copy->hash;
 }
 
 static void sha1_neon_free(ssh_hash *hash)
@@ -863,19 +882,24 @@ static void sha1_neon_write(BinarySink *bs, const void *vp, size_t len)
             sha1_neon_block(&s->core, s->blk.block);
 }
 
-static void sha1_neon_final(ssh_hash *hash, uint8_t *digest)
+static void sha1_neon_digest(ssh_hash *hash, uint8_t *digest)
 {
     sha1_neon *s = container_of(hash, sha1_neon, hash);
 
     sha1_block_pad(&s->blk, BinarySink_UPCAST(s));
     vst1q_u8(digest, vrev32q_u8(vreinterpretq_u8_u32(s->core.abcd)));
     PUT_32BIT_MSB_FIRST(digest + 16, s->core.e);
-    sha1_neon_free(hash);
 }
 
 const ssh_hashalg ssh_sha1_hw = {
-    sha1_neon_new, sha1_neon_copy, sha1_neon_final, sha1_neon_free,
-    20, 64, HASHALG_NAMES_ANNOTATED("SHA-1", "NEON accelerated"),
+    .new = sha1_neon_new,
+    .reset = sha1_neon_reset,
+    .copyfrom = sha1_neon_copyfrom,
+    .digest = sha1_neon_digest,
+    .free = sha1_neon_free,
+    .hlen = 20,
+    .blocklen = 64,
+    HASHALG_NAMES_ANNOTATED("SHA-1", "NEON accelerated"),
 };
 
 /* ----------------------------------------------------------------------
@@ -900,14 +924,20 @@ static ssh_hash *sha1_stub_new(const ssh_hashalg *alg)
 
 #define STUB_BODY { unreachable("Should never be called"); }
 
-static ssh_hash *sha1_stub_copy(ssh_hash *hash) STUB_BODY
+static void sha1_stub_reset(ssh_hash *hash) STUB_BODY
+static void sha1_stub_copyfrom(ssh_hash *hash, ssh_hash *orig) STUB_BODY
 static void sha1_stub_free(ssh_hash *hash) STUB_BODY
-static void sha1_stub_final(ssh_hash *hash, uint8_t *digest) STUB_BODY
+static void sha1_stub_digest(ssh_hash *hash, uint8_t *digest) STUB_BODY
 
 const ssh_hashalg ssh_sha1_hw = {
-    sha1_stub_new, sha1_stub_copy, sha1_stub_final, sha1_stub_free,
-    20, 64, HASHALG_NAMES_ANNOTATED(
-        "SHA-1", "!NONEXISTENT ACCELERATED VERSION!"),
+    .new = sha1_stub_new,
+    .reset = sha1_stub_reset,
+    .copyfrom = sha1_stub_copyfrom,
+    .digest = sha1_stub_digest,
+    .free = sha1_stub_free,
+    .hlen = 20,
+    .blocklen = 64,
+    HASHALG_NAMES_ANNOTATED("SHA-1", "!NONEXISTENT ACCELERATED VERSION!"),
 };
 
 #endif /* HW_SHA1 */

+ 357 - 0
source/putty/sshsha3.c

@@ -0,0 +1,357 @@
+/*
+ * SHA-3, as defined in FIPS PUB 202.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include "ssh.h"
+
+static inline uint64_t rol(uint64_t x, unsigned shift)
+{
+    unsigned L = (+shift) & 63;
+#pragma option push -w-ngu // WINSCP
+    unsigned R = (-shift) & 63;
+#pragma option pop // WINSCP
+    return (x << L) | (x >> R);
+}
+
+/*
+ * General Keccak is defined such that its state is a 5x5 array of
+ * words which can be any power-of-2 size from 1 up to 64. SHA-3 fixes
+ * on 64, and so do we.
+ *
+ * The number of rounds is defined as 12 + 2k if the word size is 2^k.
+ * Here we have 64-bit words only, so k=6, so 24 rounds always.
+ */
+typedef uint64_t keccak_core_state[5][5];
+#define NROUNDS 24               /* would differ for other word sizes */
+static const uint64_t round_constants[NROUNDS];
+static const unsigned rotation_counts[5][5];
+
+/*
+ * Core Keccak transform: just squodge the state around internally,
+ * without adding or extracting any data from it.
+ */
+static void keccak_transform(keccak_core_state A)
+{
+    union {
+        uint64_t C[5];
+        uint64_t B[5][5];
+    } u;
+
+    unsigned round; // WINSCP
+    for (round = 0; round < NROUNDS; round++) {
+        /* theta step */
+        unsigned x; // WINSCP
+        for (x = 0; x < 5; x++)
+            u.C[x] = A[x][0] ^ A[x][1] ^ A[x][2] ^ A[x][3] ^ A[x][4];
+        for (x = 0; x < 5; x++) { // WINSCP
+            uint64_t D = rol(u.C[(x+1) % 5], 1) ^ u.C[(x+4) % 5];
+            { // WINSCP
+            unsigned y; // WINSCP
+            for (y = 0; y < 5; y++)
+                A[x][y] ^= D;
+            } // WINSCP
+        }
+
+        /* rho and pi steps */
+        for (x = 0; x < 5; x++) // WINSCP
+        { // WINSCP
+            unsigned y; // WINSCP
+            for (y = 0; y < 5; y++)
+                u.B[y][(2*x+3*y) % 5] = rol(A[x][y], rotation_counts[x][y]);
+        } // WINSCP
+
+        /* chi step */
+        for (x = 0; x < 5; x++) // WINSCP
+        { // WINSCP
+            unsigned y; // WINSCP
+            for (y = 0; y < 5; y++)
+                A[x][y] = u.B[x][y] ^ (u.B[(x+2)%5][y] & ~u.B[(x+1)%5][y]);
+        } // WINSCP
+
+        /* iota step */
+        A[0][0] ^= round_constants[round];
+    }
+
+    smemclr(&u, sizeof(u));
+}
+
+typedef struct {
+    keccak_core_state A;
+    unsigned char bytes[25*8];
+    unsigned char first_pad_byte;
+    size_t bytes_got, bytes_wanted, hash_bytes;
+} keccak_state;
+
+/*
+ * Keccak accumulation function: given a piece of message, add it to
+ * the hash.
+ */
+static void keccak_accumulate(keccak_state *s, const void *vdata, size_t len)
+{
+    const unsigned char *data = (const unsigned char *)vdata;
+
+    while (len >= s->bytes_wanted - s->bytes_got) {
+        size_t b = s->bytes_wanted - s->bytes_got;
+        memcpy(s->bytes + s->bytes_got, data, b);
+        len -= b;
+        data += b;
+
+        { // WINSCP
+        size_t n = 0;
+        unsigned y; // WINSCP
+        for (y = 0; y < 5; y++) {
+            unsigned x; // WINSCP
+            for (x = 0; x < 5; x++) {
+                if (n >= s->bytes_wanted)
+                    break;
+
+                s->A[x][y] ^= GET_64BIT_LSB_FIRST(s->bytes + n);
+                n += 8;
+            }
+        }
+        keccak_transform(s->A);
+
+        s->bytes_got = 0;
+        } // WINSCP
+    }
+
+    memcpy(s->bytes + s->bytes_got, data, len);
+    s->bytes_got += len;
+}
+
+/*
+ * Keccak output function.
+ */
+static void keccak_output(keccak_state *s, void *voutput)
+{
+    unsigned char *output = (unsigned char *)voutput;
+
+    /*
+     * Add message padding.
+     */
+    {
+        unsigned char padding[25*8];
+        size_t len = s->bytes_wanted - s->bytes_got;
+        if (len == 0)
+            len = s->bytes_wanted;
+        memset(padding, 0, len);
+        padding[0] |= s->first_pad_byte;
+        padding[len-1] |= 0x80;
+        keccak_accumulate(s, padding, len);
+    }
+
+    { // WINSCP
+    size_t n = 0;
+    unsigned y; // WINSCP
+    for (y = 0; y < 5; y++) {
+        unsigned x; // WINSCP
+        for (x = 0; x < 5; x++) {
+            size_t to_copy = s->hash_bytes - n;
+            if (to_copy == 0)
+                break;
+            if (to_copy > 8)
+                to_copy = 8;
+            { // WINSCP
+            unsigned char outbytes[8];
+            PUT_64BIT_LSB_FIRST(outbytes, s->A[x][y]);
+            memcpy(output + n, outbytes, to_copy);
+            n += to_copy;
+            } // WINSCP
+        }
+    }
+    } // WINSCP
+}
+
+static void keccak_init(keccak_state *s, unsigned hashbits, unsigned ratebits,
+                        unsigned char first_pad_byte)
+{
+    int x, y;
+
+    assert(hashbits % 8 == 0);
+    assert(ratebits % 8 == 0);
+
+    s->hash_bytes = hashbits / 8;
+    s->bytes_wanted = (25 * 64 - ratebits) / 8;
+    s->bytes_got = 0;
+    s->first_pad_byte = first_pad_byte;
+
+    assert(s->bytes_wanted % 8 == 0);
+
+    for (y = 0; y < 5; y++)
+        for (x = 0; x < 5; x++)
+            s->A[x][y] = 0;
+}
+
+static void keccak_sha3_init(keccak_state *s, int hashbits)
+{
+    keccak_init(s, hashbits, hashbits * 2, 0x06);
+}
+
+static void keccak_shake_init(keccak_state *s, int parambits, int hashbits)
+{
+    keccak_init(s, hashbits, parambits * 2, 0x1f);
+}
+
+/*
+ * Keccak round constants, generated via the LFSR specified in the
+ * Keccak reference by the following piece of Python:
+
+import textwrap
+from functools import reduce
+
+rbytes = [1]
+while len(rbytes) < 7*24:
+    k = rbytes[-1] * 2
+    rbytes.append(k ^ (0x171 * (k >> 8)))
+
+rbits = [byte & 1 for byte in rbytes]
+
+rwords = [sum(rbits[i+j] << ((1 << j) - 1) for j in range(7))
+          for i in range(0, len(rbits), 7)]
+
+print(textwrap.indent("\n".join(textwrap.wrap(", ".join(
+    map("0x{:016x}".format, rwords)))), " "*4))
+
+*/
+
+static const uint64_t round_constants[24] = {
+    // WINSCP (ULL)
+    0x0000000000000001ULL, 0x0000000000008082ULL, 0x800000000000808aULL,
+    0x8000000080008000ULL, 0x000000000000808bULL, 0x0000000080000001ULL,
+    0x8000000080008081ULL, 0x8000000000008009ULL, 0x000000000000008aULL,
+    0x0000000000000088ULL, 0x0000000080008009ULL, 0x000000008000000aULL,
+    0x000000008000808bULL, 0x800000000000008bULL, 0x8000000000008089ULL,
+    0x8000000000008003ULL, 0x8000000000008002ULL, 0x8000000000000080ULL,
+    0x000000000000800aULL, 0x800000008000000aULL, 0x8000000080008081ULL,
+    0x8000000000008080ULL, 0x0000000080000001ULL, 0x8000000080008008ULL
+};
+
+/*
+ * Keccak per-element rotation counts, generated from the matrix
+ * formula in the Keccak reference by the following piece of Python:
+
+coords = [1, 0]
+while len(coords) < 26:
+    coords.append((2*coords[-2] + 3*coords[-1]) % 5)
+
+matrix = { (coords[i], coords[i+1]) : i for i in range(24) }
+matrix[0,0] = -1
+
+f = lambda t: (t+1) * (t+2) // 2 % 64
+
+for y in range(5):
+    print("    {{{}}},".format(", ".join("{:2d}".format(f(matrix[y,x]))
+                                         for x in range(5))))
+
+*/
+static const unsigned rotation_counts[5][5] = {
+    { 0, 36,  3, 41, 18},
+    { 1, 44, 10, 45,  2},
+    {62,  6, 43, 15, 61},
+    {28, 55, 25, 21, 56},
+    {27, 20, 39,  8, 14},
+};
+
+/*
+ * The PuTTY ssh_hashalg abstraction.
+ */
+struct keccak_hash {
+    keccak_state state;
+    ssh_hash hash;
+    BinarySink_IMPLEMENTATION;
+};
+
+static void keccak_BinarySink_write(BinarySink *bs, const void *p, size_t len)
+{
+    struct keccak_hash *kh = BinarySink_DOWNCAST(bs, struct keccak_hash);
+    keccak_accumulate(&kh->state, p, len);
+}
+
+static ssh_hash *keccak_new(const ssh_hashalg *alg)
+{
+    struct keccak_hash *kh = snew(struct keccak_hash);
+    kh->hash.vt = alg;
+    BinarySink_INIT(kh, keccak_BinarySink_write);
+    BinarySink_DELEGATE_INIT(&kh->hash, kh);
+    return ssh_hash_reset(&kh->hash);
+}
+
+static void keccak_free(ssh_hash *hash)
+{
+    struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash);
+    smemclr(kh, sizeof(*kh));
+    sfree(kh);
+}
+
+static void keccak_copyfrom(ssh_hash *hnew, ssh_hash *hold)
+{
+    struct keccak_hash *khold = container_of(hold, struct keccak_hash, hash);
+    struct keccak_hash *khnew = container_of(hnew, struct keccak_hash, hash);
+    khnew->state = khold->state;
+}
+
+static void keccak_digest(ssh_hash *hash, unsigned char *output)
+{
+    struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash);
+    keccak_output(&kh->state, output);
+}
+
+static void sha3_reset(ssh_hash *hash)
+{
+    struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash);
+    keccak_sha3_init(&kh->state, hash->vt->hlen * 8);
+}
+
+#define DEFINE_SHA3(bits)                       \
+    const ssh_hashalg ssh_sha3_##bits = {       \
+        /* WINSCP */ \
+        /*.new =*/ keccak_new,                      \
+        /*.reset =*/ sha3_reset,                    \
+        /*.copyfrom =*/ keccak_copyfrom,            \
+        /*.digest =*/ keccak_digest,                \
+        /*.free =*/ keccak_free,                    \
+        /*.hlen =*/ bits/8,                         \
+        /*.blocklen =*/ 200 - 2*(bits/8),           \
+        HASHALG_NAMES_BARE("SHA3-" #bits),      \
+        NULL, /* WINSCP */ \
+    }
+
+DEFINE_SHA3(224);
+DEFINE_SHA3(256);
+DEFINE_SHA3(384);
+DEFINE_SHA3(512);
+
+static void shake256_reset(ssh_hash *hash)
+{
+    struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash);
+    keccak_shake_init(&kh->state, 256, hash->vt->hlen * 8);
+}
+
+/*
+ * There is some confusion over the output length parameter for the
+ * SHAKE functions. By my reading, FIPS PUB 202 defines SHAKE256(M,d)
+ * to generate d _bits_ of output. But RFC 8032 (defining Ed448) talks
+ * about "SHAKE256(x,114)" in a context where it definitely means
+ * generating 114 _bytes_ of output.
+ *
+ * Our internal ID therefore suffixes the output length with "bytes",
+ * to be clear which we're talking about
+ */
+
+#define DEFINE_SHAKE(param, hashbytes)                          \
+    const ssh_hashalg ssh_shake##param##_##hashbytes##bytes = { \
+        /* WINSCP */ \
+        /*.new =*/ keccak_new,                                      \
+        /*.reset =*/ shake##param##_reset,                          \
+        /*.copyfrom =*/ keccak_copyfrom,                            \
+        /*.digest =*/ keccak_digest,                                \
+        /*.free =*/ keccak_free,                                    \
+        /*.hlen =*/ hashbytes,                                      \
+        /*.blocklen =*/ 0,                                          \
+        HASHALG_NAMES_BARE("SHAKE" #param),                     \
+        NULL, /*NULL*/ \
+    }
+
+DEFINE_SHAKE(256, 114);

+ 12 - 12
source/putty/sshshare.c

@@ -1904,11 +1904,9 @@ void share_activate(ssh_sharing_state *sharestate,
 }
 
 static const PlugVtable ssh_sharing_conn_plugvt = {
-    NULL, /* no log function, because that's for outgoing connections */
-    share_closing,
-    share_receive,
-    share_sent,
-    NULL /* no accepting function, because we've already done it */
+    .closing = share_closing,
+    .receive = share_receive,
+    .sent = share_sent,
 };
 
 static int share_listen_accepting(Plug *plug,
@@ -1942,7 +1940,7 @@ static int share_listen_accepting(Plug *plug,
         return err != NULL;
     }
 
-    sk_set_frozen(cs->sock, 0);
+    sk_set_frozen(cs->sock, false);
 
     add234(cs->parent->connections, cs);
 
@@ -1995,9 +1993,14 @@ static int share_listen_accepting(Plug *plug,
  */
 char *ssh_share_sockname(const char *host, int port, Conf *conf)
 {
-    char *username = get_remote_username(conf);
+    char *username = NULL;
     char *sockname;
 
+    /* Include the username we're logging in as in the hash, unless
+     * we're using a protocol for which it's completely irrelevant. */
+    if (conf_get_int(conf, CONF_protocol) != PROT_SSHCONN)
+        username = get_remote_username(conf);
+
     if (port == 22) {
         if (username)
             sockname = dupprintf("%s@%s", username, host);
@@ -2043,11 +2046,8 @@ bool ssh_share_test_for_upstream(const char *host, int port, Conf *conf)
 }
 
 static const PlugVtable ssh_sharing_listen_plugvt = {
-    NULL, /* no log function, because that's for outgoing connections */
-    share_listen_closing,
-    NULL, /* no receive function on a listening socket */
-    NULL, /* no sent function on a listening socket */
-    share_listen_accepting
+    .closing = share_listen_closing,
+    .accepting = share_listen_accepting,
 };
 
 void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate,

+ 128 - 0
source/putty/sshutils.c

@@ -0,0 +1,128 @@
+/*
+ * Supporting routines used in common by all the various components of
+ * the SSH system.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sshchan.h"
+
+/* ----------------------------------------------------------------------
+ * Centralised standard methods for other channel implementations to
+ * borrow.
+ */
+
+void chan_remotely_opened_confirmation(Channel *chan)
+{
+    unreachable("this channel type should never receive OPEN_CONFIRMATION");
+}
+
+void chan_remotely_opened_failure(Channel *chan, const char *errtext)
+{
+    unreachable("this channel type should never receive OPEN_FAILURE");
+}
+
+bool chan_default_want_close(
+    Channel *chan, bool sent_local_eof, bool rcvd_remote_eof)
+{
+    /*
+     * Default close policy: we start initiating the CHANNEL_CLOSE
+     * procedure as soon as both sides of the channel have seen EOF.
+     */
+    return sent_local_eof && rcvd_remote_eof;
+}
+
+bool chan_no_exit_status(Channel *chan, int status)
+{
+    return false;
+}
+
+bool chan_no_exit_signal(
+    Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg)
+{
+    return false;
+}
+
+bool chan_no_exit_signal_numeric(
+    Channel *chan, int signum, bool core_dumped, ptrlen msg)
+{
+    return false;
+}
+
+bool chan_no_run_shell(Channel *chan)
+{
+    return false;
+}
+
+bool chan_no_run_command(Channel *chan, ptrlen command)
+{
+    return false;
+}
+
+bool chan_no_run_subsystem(Channel *chan, ptrlen subsys)
+{
+    return false;
+}
+
+bool chan_no_enable_x11_forwarding(
+    Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata,
+    unsigned screen_number)
+{
+    return false;
+}
+
+bool chan_no_enable_agent_forwarding(Channel *chan)
+{
+    return false;
+}
+
+bool chan_no_allocate_pty(
+    Channel *chan, ptrlen termtype, unsigned width, unsigned height,
+    unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes)
+{
+    return false;
+}
+
+bool chan_no_set_env(Channel *chan, ptrlen var, ptrlen value)
+{
+    return false;
+}
+
+bool chan_no_send_break(Channel *chan, unsigned length)
+{
+    return false;
+}
+
+bool chan_no_send_signal(Channel *chan, ptrlen signame)
+{
+    return false;
+}
+
+bool chan_no_change_window_size(
+    Channel *chan, unsigned width, unsigned height,
+    unsigned pixwidth, unsigned pixheight)
+{
+    return false;
+}
+
+void chan_no_request_response(Channel *chan, bool success)
+{
+    unreachable("this channel type should never send a want-reply request");
+}
+
+/* ----------------------------------------------------------------------
+ * Other miscellaneous utility functions.
+ */
+
+void free_rportfwd(struct ssh_rportfwd *rpf)
+{
+    if (rpf) {
+        sfree(rpf->log_description);
+        sfree(rpf->shost);
+        sfree(rpf->dhost);
+        sfree(rpf);
+    }
+}

+ 7 - 7
source/putty/sshverstring.c

@@ -45,13 +45,13 @@ static PktOut *ssh_verstring_new_pktout(int type);
 static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp,
                                           const char *msg, int category);
 
-static const struct BinaryPacketProtocolVtable ssh_verstring_vtable = {
-    ssh_verstring_free,
-    ssh_verstring_handle_input,
-    ssh_verstring_handle_output,
-    ssh_verstring_new_pktout,
-    ssh_verstring_queue_disconnect,
-    0xFFFFFFFF, /* no special packet size limit for this bpp */
+static const BinaryPacketProtocolVtable ssh_verstring_vtable = {
+    .free = ssh_verstring_free,
+    .handle_input = ssh_verstring_handle_input,
+    .handle_output = ssh_verstring_handle_output,
+    .new_pktout = ssh_verstring_new_pktout,
+    .queue_disconnect = ssh_verstring_queue_disconnect,
+    .packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */
 };
 
 static void ssh_detect_bugs(struct ssh_verstring_state *s);

+ 10 - 10
source/putty/sshzlib.c

@@ -730,7 +730,7 @@ static struct zlib_table *zlib_mkonetab(int *codes, unsigned char *lengths,
     int pfxmask = (1 << pfxbits) - 1;
     int nbits, i, j, code;
 
-    tab->table = snewn(1 << bits, struct zlib_tableentry);
+    tab->table = snewn((size_t)1 << bits, struct zlib_tableentry);
     tab->mask = (1 << bits) - 1;
 
     for (code = 0; code <= tab->mask; code++) {
@@ -1241,13 +1241,13 @@ bool zlib_decompress_block(ssh_decompressor *dc,
 }
 
 const ssh_compression_alg ssh_zlib = {
-    "zlib",
-    "[email protected]", /* delayed version */
-    zlib_compress_init,
-    zlib_compress_cleanup,
-    zlib_compress_block,
-    zlib_decompress_init,
-    zlib_decompress_cleanup,
-    zlib_decompress_block,
-    "zlib (RFC1950)"
+    .name = "zlib",
+    .delayed_name = "[email protected]", /* delayed version */
+    .compress_new = zlib_compress_init,
+    .compress_free = zlib_compress_cleanup,
+    .compress = zlib_compress_block,
+    .decompress_new = zlib_decompress_init,
+    .decompress_free = zlib_decompress_cleanup,
+    .decompress = zlib_decompress_block,
+    .text_name = "zlib (RFC1950)",
 };

+ 8 - 8
source/putty/tree234.c

@@ -1211,12 +1211,12 @@ int chknode(chkctx * ctx, int level, node234 * node,
 
 void verify(void)
 {
-    chkctx ctx;
+    chkctx ctx[1];
     int i;
     void *p;
 
-    ctx.treedepth = -1;                /* depth unknown yet */
-    ctx.elemcount = 0;                 /* no elements seen yet */
+    ctx->treedepth = -1;                /* depth unknown yet */
+    ctx->elemcount = 0;                 /* no elements seen yet */
     /*
      * Verify validity of tree properties.
      */
@@ -1225,7 +1225,7 @@ void verify(void)
             error("root->parent is %p should be null", tree->root->parent);
         chknode(&ctx, 0, tree->root, NULL, NULL);
     }
-    printf("tree depth: %d\n", ctx.treedepth);
+    printf("tree depth: %d\n", ctx->treedepth);
     /*
      * Enumerate the tree and ensure it matches up to the array.
      */
@@ -1236,17 +1236,17 @@ void verify(void)
             error("enum at position %d: array says %s, tree says %s",
                   i, array[i], p);
     }
-    if (ctx.elemcount != i) {
+    if (ctx->elemcount != i) {
         error("tree really contains %d elements, enum gave %d",
-              ctx.elemcount, i);
+              ctx->elemcount, i);
     }
     if (i < arraylen) {
         error("enum gave only %d elements, array has %d", i, arraylen);
     }
     i = count234(tree);
-    if (ctx.elemcount != i) {
+    if (ctx->elemcount != i) {
         error("tree really contains %d elements, count234 gave %d",
-              ctx.elemcount, i);
+              ctx->elemcount, i);
     }
 }
 

+ 73 - 2
source/putty/utils.c

@@ -17,6 +17,7 @@
 
 #include "defs.h"
 #include "misc.h"
+#include "ssh.h"
 
 /*
  * Parse a string block size specification. This is approximately a
@@ -872,7 +873,7 @@ void debug_memdump(const void *buf, int len, bool L)
                 dputs("   ");          /* 3 spaces */
                 foo[i] = ' ';
             } else {
-                debug_printf("%c%02.2x",
+                debug_printf("%c%2.2x",
                         &p[i] != (unsigned char *) buf
                         && i % 4 ? '.' : ' ', p[i]
                     );
@@ -1035,7 +1036,7 @@ char *mkstr(ptrlen pl)
 
 bool strstartswith(const char *s, const char *t)
 {
-    return !memcmp(s, t, strlen(t));
+    return !strncmp(s, t, strlen(t));
 }
 
 bool strendswith(const char *s, const char *t)
@@ -1065,3 +1066,73 @@ size_t encode_utf8(void *output, unsigned long ch)
     }
     return p - start;
 }
+
+void write_c_string_literal(FILE *fp, ptrlen str)
+{
+    for (const char *p = str.ptr; p < (const char *)str.ptr + str.len; p++) {
+        char c = *p;
+
+        if (c == '\n')
+            fputs("\\n", fp);
+        else if (c == '\r')
+            fputs("\\r", fp);
+        else if (c == '\t')
+            fputs("\\t", fp);
+        else if (c == '\b')
+            fputs("\\b", fp);
+        else if (c == '\\')
+            fputs("\\\\", fp);
+        else if (c == '"')
+            fputs("\\\"", fp);
+        else if (c >= 32 && c <= 126)
+            fputc(c, fp);
+        else
+            fprintf(fp, "\\%03o", (unsigned char)c);
+    }
+}
+
+void memxor(uint8_t *out, const uint8_t *in1, const uint8_t *in2, size_t size)
+{
+    switch (size & 15) {
+      case 0:
+        while (size >= 16) {
+            size -= 16;
+                   *out++ = *in1++ ^ *in2++;
+          case 15: *out++ = *in1++ ^ *in2++;
+          case 14: *out++ = *in1++ ^ *in2++;
+          case 13: *out++ = *in1++ ^ *in2++;
+          case 12: *out++ = *in1++ ^ *in2++;
+          case 11: *out++ = *in1++ ^ *in2++;
+          case 10: *out++ = *in1++ ^ *in2++;
+          case 9:  *out++ = *in1++ ^ *in2++;
+          case 8:  *out++ = *in1++ ^ *in2++;
+          case 7:  *out++ = *in1++ ^ *in2++;
+          case 6:  *out++ = *in1++ ^ *in2++;
+          case 5:  *out++ = *in1++ ^ *in2++;
+          case 4:  *out++ = *in1++ ^ *in2++;
+          case 3:  *out++ = *in1++ ^ *in2++;
+          case 2:  *out++ = *in1++ ^ *in2++;
+          case 1:  *out++ = *in1++ ^ *in2++;
+        }
+    }
+}
+
+FingerprintType ssh2_pick_fingerprint(
+    char **fingerprints, FingerprintType preferred_type)
+{
+    /*
+     * Keys are either SSH-2, in which case we have all fingerprint
+     * types, or SSH-1, in which case we have only MD5. So we return
+     * the default type if we can, or MD5 if that's all we have; no
+     * need for a fully general preference-list system.
+     */
+    FingerprintType fptype = fingerprints[preferred_type] ?
+        preferred_type : SSH_FPTYPE_MD5;
+    assert(fingerprints[fptype]);
+    return fptype;
+}
+
+FingerprintType ssh2_pick_default_fingerprint(char **fingerprints)
+{
+    return ssh2_pick_fingerprint(fingerprints, SSH_FPTYPE_DEFAULT);
+}

+ 5 - 5
source/putty/version.h

@@ -1,6 +1,6 @@
 /* Generated by automated build script */
-#define RELEASE 0.74
-#define TEXTVER "Release 0.74"
-#define SSHVER "-Release-0.74"
-#define BINARY_VERSION 0,74,0,0
-#define SOURCE_COMMIT "014d4fb151369f255b3debed7d15a154fd9036f5"
+#define RELEASE 0.75
+#define TEXTVER "Release 0.75"
+#define SSHVER "-Release-0.75"
+#define BINARY_VERSION 0,75,0,0
+#define SOURCE_COMMIT "c72200ff8851b0d95574b8a8a88a2780a243c66c"

+ 93 - 0
source/putty/windows/wincapi.c

@@ -0,0 +1,93 @@
+/*
+ * wincapi.c: implementation of wincapi.h.
+ */
+
+#include "putty.h"
+
+#if !defined NO_SECURITY
+
+#include "putty.h"
+#include "ssh.h"
+
+#include "wincapi.h"
+
+DEF_WINDOWS_FUNCTION(CryptProtectMemory);
+
+bool got_crypt(void)
+{
+    static bool attempted = false;
+    static bool successful;
+    static HMODULE crypt;
+
+    if (!attempted) {
+        attempted = true;
+        crypt = load_system32_dll("crypt32.dll");
+        successful = crypt &&
+            GET_WINDOWS_FUNCTION(crypt, CryptProtectMemory);
+    }
+    return successful;
+}
+
+char *capi_obfuscate_string(const char *realname)
+{
+    char *cryptdata;
+    int cryptlen;
+    unsigned char digest[32];
+    char retbuf[65];
+    int i;
+
+    cryptlen = strlen(realname) + 1;
+    cryptlen += CRYPTPROTECTMEMORY_BLOCK_SIZE - 1;
+    cryptlen /= CRYPTPROTECTMEMORY_BLOCK_SIZE;
+    cryptlen *= CRYPTPROTECTMEMORY_BLOCK_SIZE;
+
+    cryptdata = snewn(cryptlen, char);
+    memset(cryptdata, 0, cryptlen);
+    strcpy(cryptdata, realname);
+
+    /*
+     * CRYPTPROTECTMEMORY_CROSS_PROCESS causes CryptProtectMemory to
+     * use the same key in all processes with this user id, meaning
+     * that the next PuTTY process calling this function with the same
+     * input will get the same data.
+     *
+     * (Contrast with CryptProtectData, which invents a new session
+     * key every time since its API permits returning more data than
+     * was input, so calling _that_ and hashing the output would not
+     * be stable.)
+     *
+     * We don't worry too much if this doesn't work for some reason.
+     * Omitting this step still has _some_ privacy value (in that
+     * another user can test-hash things to confirm guesses as to
+     * where you might be connecting to, but cannot invert SHA-256 in
+     * the absence of any plausible guess). So we don't abort if we
+     * can't call CryptProtectMemory at all, or if it fails.
+     */
+    if (got_crypt())
+        p_CryptProtectMemory(cryptdata, cryptlen,
+                             CRYPTPROTECTMEMORY_CROSS_PROCESS);
+
+    /*
+     * We don't want to give away the length of the hostname either,
+     * so having got it back out of CryptProtectMemory we now hash it.
+     */
+    {
+        ssh_hash *h = ssh_hash_new(&ssh_sha256);
+        put_string(h, cryptdata, cryptlen);
+        ssh_hash_final(h, digest);
+    }
+
+    sfree(cryptdata);
+
+    /*
+     * Finally, make printable.
+     */
+    for (i = 0; i < 32; i++) {
+        sprintf(retbuf + 2*i, "%02x", digest[i]);
+        /* the last of those will also write the trailing NUL */
+    }
+
+    return dupstr(retbuf);
+}
+
+#endif /* !defined NO_SECURITY */

+ 31 - 0
source/putty/windows/wincapi.h

@@ -0,0 +1,31 @@
+/*
+ * wincapi.h: Windows Crypto API functions defined in wincapi.c that
+ * use the crypt32 library. Also centralises the machinery for
+ * dynamically loading that library, and our own functions using that
+ * in turn.
+ */
+
+#if !defined NO_SECURITY
+
+DECL_WINDOWS_FUNCTION(extern, BOOL, CryptProtectMemory, (LPVOID,DWORD,DWORD));
+
+bool got_crypt(void);
+
+/*
+ * Function to obfuscate an input string into something usable as a
+ * pathname for a Windows named pipe. Uses CryptProtectMemory to make
+ * the obfuscation depend on a key Windows stores for the owning user,
+ * and then hashes the string as well to make it have a manageable
+ * length and be composed of filename-legal characters.
+ *
+ * Rationale: Windows's named pipes all live in the same namespace, so
+ * one user can see what pipes another user has open. This is an
+ * undesirable privacy leak: in particular, if we used unobfuscated
+ * names for the connection-sharing pipe names, it would permit one
+ * user to know what username@host another user is SSHing to.
+ *
+ * The returned string is dynamically allocated.
+ */
+char *capi_obfuscate_string(const char *realname);
+
+#endif

+ 1 - 1
source/putty/windows/winhandl.c

@@ -201,7 +201,7 @@ static DWORD WINAPI handle_input_threadfunc(void *param)
 }
 
 /*
- * This is called after a succcessful read, or from the
+ * This is called after a successful read, or from the
  * `unthrottle' function. It decides whether or not to begin a new
  * read operation.
  */

+ 9 - 9
source/putty/windows/winhsock.c

@@ -293,7 +293,7 @@ static SocketPeerInfo *sk_handle_peer_info(Socket *s)
 
     if (!kernel32_module) {
         kernel32_module = load_system32_dll("kernel32.dll");
-#if (defined _MSC_VER && _MSC_VER < 1900) || defined __MINGW32__ || defined COVERITY
+#if (defined _MSC_VER && _MSC_VER < 1900) || defined __MINGW32__
         /* For older Visual Studio, and MinGW too (at least as of
          * Ubuntu 16.04), this function isn't available in the header
          * files to type-check. Ditto the toolchain I use for
@@ -325,14 +325,14 @@ static SocketPeerInfo *sk_handle_peer_info(Socket *s)
 }
 
 static const SocketVtable HandleSocket_sockvt = {
-    sk_handle_plug,
-    sk_handle_close,
-    sk_handle_write,
-    sk_handle_write_oob,
-    sk_handle_write_eof,
-    sk_handle_set_frozen,
-    sk_handle_socket_error,
-    sk_handle_peer_info,
+    .plug = sk_handle_plug,
+    .close = sk_handle_close,
+    .write = sk_handle_write,
+    .write_oob = sk_handle_write_oob,
+    .write_eof = sk_handle_write_eof,
+    .set_frozen = sk_handle_set_frozen,
+    .socket_error = sk_handle_socket_error,
+    .peer_info = sk_handle_peer_info,
 };
 
 Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H,

+ 3 - 5
source/putty/windows/winmisc.c

@@ -194,11 +194,9 @@ void dll_hijacking_protection(void)
 
     if (!kernel32_module) {
         kernel32_module = load_system32_dll("kernel32.dll");
-#if (defined _MSC_VER && _MSC_VER < 1900) || defined COVERITY || defined MPEXT
-        /* For older Visual Studio, and also for the system I
-         * currently use for Coveritying the Windows code, this
-         * function isn't available in the header files to
-         * type-check */
+#if (defined _MSC_VER && _MSC_VER < 1900) || defined WINSCP
+        /* For older Visual Studio, this function isn't available in
+         * the header files to type-check */
         GET_WINDOWS_FUNCTION_NO_TYPECHECK(
             kernel32_module, SetDefaultDllDirectories);
 #else

+ 8 - 0
source/putty/windows/winmiscs.c

@@ -266,6 +266,14 @@ bool platform_sha1_hw_available(void)
     return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE);
 }
 
+bool platform_sha512_hw_available(void)
+{
+    /* As of 2020-12-24, as far as I can tell from docs.microsoft.com,
+     * Windows on Arm does not yet provide a PF_ARM_V8_* flag for the
+     * SHA-512 architecture extension. */
+    return false;
+}
+
 #endif
 
 bool is_console_handle(HANDLE handle)

+ 75 - 77
source/putty/windows/winnet.c

@@ -16,6 +16,7 @@
 #include "putty.h"
 #include "network.h"
 #include "tree234.h"
+#include "ssh.h"
 
 #ifdef MPEXT
 // ws2tcpip.h does not compile without _MSC_VER defined
@@ -231,12 +232,11 @@ static bool sk_startup(int hi, int lo)
     return true;
 }
 
-/* Actually define this function pointer, which won't have been
- * defined alongside all the others by PUTTY_DO_GLOBALS because of the
- * annoying winelib header-ordering issue. (See comment in winstuff.h.) */
-DECL_WINDOWS_FUNCTION(/* empty */, int, select,
-                      (int, fd_set FAR *, fd_set FAR *,
-                       fd_set FAR *, const struct timeval FAR *));
+DEF_WINDOWS_FUNCTION(WSAAsyncSelect);
+DEF_WINDOWS_FUNCTION(WSAEventSelect);
+DEF_WINDOWS_FUNCTION(WSAGetLastError);
+DEF_WINDOWS_FUNCTION(WSAEnumNetworkEvents);
+DEF_WINDOWS_FUNCTION(select);
 
 void sk_init(void)
 {
@@ -290,32 +290,21 @@ void sk_init(void)
     GET_WINDOWS_FUNCTION(winsock_module, WSAStartup);
     GET_WINDOWS_FUNCTION(winsock_module, WSACleanup);
     GET_WINDOWS_FUNCTION(winsock_module, closesocket);
-#ifndef COVERITY
     GET_WINDOWS_FUNCTION(winsock_module, ntohl);
     GET_WINDOWS_FUNCTION(winsock_module, htonl);
     GET_WINDOWS_FUNCTION(winsock_module, htons);
     GET_WINDOWS_FUNCTION(winsock_module, ntohs);
     GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gethostname);
-#else
-    /* The toolchain I use for Windows Coverity builds doesn't know
-     * the type signatures of these */
-    GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, ntohl);
-    GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, htonl);
-    GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, htons);
-    GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, ntohs);
-    GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gethostname);
-#endif
     GET_WINDOWS_FUNCTION(winsock_module, gethostbyname);
     GET_WINDOWS_FUNCTION(winsock_module, getservbyname);
     GET_WINDOWS_FUNCTION(winsock_module, inet_addr);
     GET_WINDOWS_FUNCTION(winsock_module, inet_ntoa);
-#if (defined _MSC_VER && _MSC_VER < 1900) || defined __MINGW32__
     /* Older Visual Studio, and MinGW as of Ubuntu 16.04, don't know
-     * about this function at all, so can't type-check it */
+     * about this function at all, so can't type-check it. Also there
+     * seems to be some disagreement in the VS headers about whether
+     * the second argument is void * or const void *, so I omit the
+     * type check. */
     GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, inet_ntop);
-#else
-    GET_WINDOWS_FUNCTION(winsock_module, inet_ntop);
-#endif
     GET_WINDOWS_FUNCTION(winsock_module, connect);
     GET_WINDOWS_FUNCTION(winsock_module, bind);
     #ifdef MPEXT
@@ -571,8 +560,7 @@ SockAddr *sk_namelookup(const char *host, char **canonicalname,
         strncpy(realhost, host, sizeof(realhost));
     }
     realhost[lenof(realhost)-1] = '\0';
-    *canonicalname = snewn(1+strlen(realhost), char);
-    strcpy(*canonicalname, realhost);
+    *canonicalname = dupstr(realhost);
     return ret;
 }
 
@@ -852,20 +840,20 @@ static const char *sk_net_socket_error(Socket *s);
 static SocketPeerInfo *sk_net_peer_info(Socket *s);
 
 static const SocketVtable NetSocket_sockvt = {
-    sk_net_plug,
-    sk_net_close,
-    sk_net_write,
-    sk_net_write_oob,
-    sk_net_write_eof,
-    sk_net_set_frozen,
-    sk_net_socket_error,
-    sk_net_peer_info,
+    .plug = sk_net_plug,
+    .close = sk_net_close,
+    .write = sk_net_write,
+    .write_oob = sk_net_write_oob,
+    .write_eof = sk_net_write_eof,
+    .set_frozen = sk_net_set_frozen,
+    .socket_error = sk_net_socket_error,
+    .peer_info = sk_net_peer_info,
 };
 
 static Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug)
 {
     DWORD err;
-    char *errstr;
+    const char *errstr;
     NetSocket *ret;
 
     /*
@@ -926,7 +914,7 @@ static DWORD try_connect(NetSocket *sock,
 #endif
     SOCKADDR_IN a;
     DWORD err;
-    char *errstr;
+    const char *errstr;
     short localport;
     int family;
 #ifdef MPEXT
@@ -945,7 +933,8 @@ static DWORD try_connect(NetSocket *sock,
     {
         SockAddr thisaddr = sk_extractaddr_tmp(
             sock->addr, &sock->step);
-        plug_log(sock->plug, 0, &thisaddr, sock->port, NULL, 0);
+        plug_log(sock->plug, PLUGLOG_CONNECT_TRYING,
+                 &thisaddr, sock->port, NULL, 0);
     }
 
     /*
@@ -1141,6 +1130,9 @@ static DWORD try_connect(NetSocket *sock,
          * and we should set the socket as writable.
          */
         sock->writable = true;
+        SockAddr thisaddr = sk_extractaddr_tmp(sock->addr, &sock->step);
+        plug_log(sock->plug, PLUGLOG_CONNECT_SUCCESS,
+                 &thisaddr, sock->port, NULL, 0);
     }
 
 #ifdef MPEXT
@@ -1172,7 +1164,8 @@ static DWORD try_connect(NetSocket *sock,
     if (err) {
         SockAddr thisaddr = sk_extractaddr_tmp(
             sock->addr, &sock->step);
-        plug_log(sock->plug, 1, &thisaddr, sock->port, sock->error, err);
+        plug_log(sock->plug, PLUGLOG_CONNECT_FAILED,
+                 &thisaddr, sock->port, sock->error, err);
     }
     return err;
 }
@@ -1240,7 +1233,7 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
     SOCKADDR_IN a;
 
     DWORD err;
-    char *errstr;
+    const char *errstr;
     NetSocket *ret;
     int retcode;
 
@@ -1641,7 +1634,7 @@ void select_result(WPARAM wParam, LPARAM lParam)
         if (s->addr) {
             SockAddr thisaddr = sk_extractaddr_tmp(
                 s->addr, &s->step);
-            plug_log(s->plug, 1, &thisaddr, s->port,
+            plug_log(s->plug, PLUGLOG_CONNECT_FAILED, &thisaddr, s->port,
                      winsock_error_string(err), err);
             while (err && s->addr && sk_nextaddr(s->addr, &s->step)) {
                 err = try_connect(s
@@ -1664,12 +1657,18 @@ void select_result(WPARAM wParam, LPARAM lParam)
       case FD_CONNECT:
         s->connected = true;
         s->writable = true;
+
         /*
-         * Once a socket is connected, we can stop falling
-         * back through the candidate addresses to connect
-         * to.
+         * Once a socket is connected, we can stop falling back
+         * through the candidate addresses to connect to. But first,
+         * let the plug know we were successful.
          */
         if (s->addr) {
+            SockAddr thisaddr = sk_extractaddr_tmp(
+                s->addr, &s->step);
+            plug_log(s->plug, PLUGLOG_CONNECT_SUCCESS,
+                     &thisaddr, s->port, NULL, 0);
+
             sk_addr_free(s->addr);
             s->addr = NULL;
         }
@@ -1733,17 +1732,16 @@ void select_result(WPARAM wParam, LPARAM lParam)
             plug_receive(s->plug, 2, buf, ret);
         }
         break;
-      case FD_WRITE:
-        {
-            int bufsize_before, bufsize_after;
-            s->writable = true;
-            bufsize_before = s->sending_oob + bufchain_size(&s->output_data);
-            try_send(s);
-            bufsize_after = s->sending_oob + bufchain_size(&s->output_data);
-            if (bufsize_after < bufsize_before)
-                plug_sent(s->plug, bufsize_after);
-        }
+      case FD_WRITE: {
+        int bufsize_before, bufsize_after;
+        s->writable = true;
+        bufsize_before = s->sending_oob + bufchain_size(&s->output_data);
+        try_send(s);
+        bufsize_after = s->sending_oob + bufchain_size(&s->output_data);
+        if (bufsize_after < bufsize_before)
+            plug_sent(s->plug, bufsize_after);
         break;
+      }
       case FD_CLOSE:
         /* Signal a close on the socket. First read any outstanding data. */
         do {
@@ -1763,42 +1761,42 @@ void select_result(WPARAM wParam, LPARAM lParam)
 	    }
         } while (ret > 0);
         return;
-       case FD_ACCEPT:
-        {
+      case FD_ACCEPT: {
 #ifdef NO_IPV6
-            struct sockaddr_in isa;
+        struct sockaddr_in isa;
 #else
-            struct sockaddr_storage isa;
+        struct sockaddr_storage isa;
 #endif
-            int addrlen = sizeof(isa);
-            SOCKET t;  /* socket of connection */
-            accept_ctx_t actx;
-
-            memset(&isa, 0, sizeof(isa));
-            err = 0;
-            t = p_accept(s->s,(struct sockaddr *)&isa,&addrlen);
-            if (t == INVALID_SOCKET)
-            {
-                err = p_WSAGetLastError();
-                if (err == WSATRY_AGAIN)
-                    break;
-            }
+        int addrlen = sizeof(isa);
+        SOCKET t;  /* socket of connection */
+        accept_ctx_t actx;
+
+        memset(&isa, 0, sizeof(isa));
+        err = 0;
+        t = p_accept(s->s,(struct sockaddr *)&isa,&addrlen);
+        if (t == INVALID_SOCKET)
+        {
+          err = p_WSAGetLastError();
+          if (err == WSATRY_AGAIN)
+              break;
+        }
 
-            actx.p = (void *)t;
+        actx.p = (void *)t;
 
 #ifndef NO_IPV6
-            if (isa.ss_family == AF_INET &&
-                s->localhost_only &&
-                !ipv4_is_local_addr(((struct sockaddr_in *)&isa)->sin_addr))
+        if (isa.ss_family == AF_INET &&
+            s->localhost_only &&
+            !ipv4_is_local_addr(((struct sockaddr_in *)&isa)->sin_addr))
 #else
-            if (s->localhost_only && !ipv4_is_local_addr(isa.sin_addr))
+        if (s->localhost_only && !ipv4_is_local_addr(isa.sin_addr))
 #endif
-            {
-                p_closesocket(t);      /* dodgy WinSock let nonlocal through */
-            } else if (plug_accepting(s->plug, sk_net_accept, actx)) {
-                p_closesocket(t);      /* denied or error */
-            }
+        {
+          p_closesocket(t);      /* dodgy WinSock let nonlocal through */
+        } else if (plug_accepting(s->plug, sk_net_accept, actx)) {
+          p_closesocket(t);      /* denied or error */
         }
+        break;
+      }
     }
 }
 

+ 98 - 0
source/putty/windows/winnpc.c

@@ -0,0 +1,98 @@
+/*
+ * Windows support module which deals with being a named-pipe client.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#include "tree234.h"
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+#include "ssh.h"
+
+#if !defined NO_SECURITY
+
+#include "winsecur.h"
+
+HANDLE connect_to_named_pipe(const char *pipename, char **err)
+{
+    HANDLE pipehandle;
+    PSID usersid, pipeowner;
+    PSECURITY_DESCRIPTOR psd;
+
+    assert(strncmp(pipename, "\\\\.\\pipe\\", 9) == 0);
+    assert(strchr(pipename + 9, '\\') == NULL);
+
+    while (1) {
+        pipehandle = CreateFile(pipename, GENERIC_READ | GENERIC_WRITE,
+                                0, NULL, OPEN_EXISTING,
+                                FILE_FLAG_OVERLAPPED, NULL);
+
+        if (pipehandle != INVALID_HANDLE_VALUE)
+            break;
+
+        if (GetLastError() != ERROR_PIPE_BUSY) {
+            *err = dupprintf(
+                "Unable to open named pipe '%s': %s",
+                pipename, win_strerror(GetLastError()));
+            return INVALID_HANDLE_VALUE;
+        }
+
+        /*
+         * If we got ERROR_PIPE_BUSY, wait for the server to
+         * create a new pipe instance. (Since the server is
+         * expected to be winnps.c, which will do that immediately
+         * after a previous connection is accepted, that shouldn't
+         * take excessively long.)
+         */
+        if (!WaitNamedPipe(pipename, NMPWAIT_USE_DEFAULT_WAIT)) {
+            *err = dupprintf(
+                "Error waiting for named pipe '%s': %s",
+                pipename, win_strerror(GetLastError()));
+            return INVALID_HANDLE_VALUE;
+        }
+    }
+
+    if ((usersid = get_user_sid()) == NULL) {
+        CloseHandle(pipehandle);
+        *err = dupprintf(
+            "Unable to get user SID: %s", win_strerror(GetLastError()));
+        return INVALID_HANDLE_VALUE;
+    }
+
+    if (p_GetSecurityInfo(pipehandle, SE_KERNEL_OBJECT,
+                          OWNER_SECURITY_INFORMATION,
+                          &pipeowner, NULL, NULL, NULL,
+                          &psd) != ERROR_SUCCESS) {
+        CloseHandle(pipehandle);
+        *err = dupprintf(
+            "Unable to get named pipe security information: %s",
+            win_strerror(GetLastError()));
+        return INVALID_HANDLE_VALUE;
+    }
+
+    if (!EqualSid(pipeowner, usersid)) {
+        CloseHandle(pipehandle);
+        LocalFree(psd);
+        *err = dupprintf(
+            "Owner of named pipe '%s' is not us", pipename);
+        return INVALID_HANDLE_VALUE;
+    }
+
+    LocalFree(psd);
+
+    return pipehandle;
+}
+
+Socket *new_named_pipe_client(const char *pipename, Plug *plug)
+{
+    char *err = NULL;
+    HANDLE pipehandle = connect_to_named_pipe(pipename, &err);
+    if (pipehandle == INVALID_HANDLE_VALUE)
+        return new_error_socket_consume_string(plug, err);
+    else
+        return make_handle_socket(pipehandle, pipehandle, NULL, plug, true);
+}
+
+#endif /* !defined NO_SECURITY */

+ 220 - 12
source/putty/windows/winpgntc.c

@@ -11,11 +11,12 @@
 
 #ifndef NO_SECURITY
 #include "winsecur.h"
+#include "wincapi.h"
 #endif
 
 #define AGENT_COPYDATA_ID 0x804e50ba   /* random goop */
 
-bool agent_exists(void)
+static bool wm_copydata_agent_exists(void)
 {
     HWND hwnd;
     hwnd = FindWindow("Pageant", "Pageant");
@@ -25,14 +26,7 @@ bool agent_exists(void)
         return true;
 }
 
-void agent_cancel_query(agent_pending_query *q)
-{
-    unreachable("Windows agent queries are never asynchronous!");
-}
-
-agent_pending_query *agent_query(
-    strbuf *query, void **out, int *outlen,
-    void (*callback)(void *, void *, int), void *callback_ctx)
+static void wm_copydata_agent_query(strbuf *query, void **out, int *outlen)
 {
     HWND hwnd;
     char *mapname;
@@ -48,11 +42,11 @@ agent_pending_query *agent_query(
     *outlen = 0;
 
     if (query->len > AGENT_MAX_MSGLEN)
-        return NULL;                   /* query too large */
+        return;                        /* query too large */
 
     hwnd = FindWindow("Pageant", "Pageant");
     if (!hwnd)
-        return NULL;                   /* *out == NULL, so failure */
+        return;                        /* *out == NULL, so failure */
     mapname = dupprintf("PageantRequest%08x", (unsigned)GetCurrentThreadId());
 
     psa = NULL;
@@ -93,7 +87,7 @@ agent_pending_query *agent_query(
                                 0, AGENT_MAX_MSGLEN, mapname);
     if (filemap == NULL || filemap == INVALID_HANDLE_VALUE) {
         sfree(mapname);
-        return NULL;                   /* *out == NULL, so failure */
+        return;                        /* *out == NULL, so failure */
     }
     p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
     strbuf_finalise_agent_query(query);
@@ -133,5 +127,219 @@ agent_pending_query *agent_query(
     sfree(mapname);
     if (psd)
         LocalFree(psd);
+}
+
+#ifndef NO_SECURITY
+
+char *agent_named_pipe_name(void)
+{
+    char *username, *suffix, *pipename;
+    username = get_username();
+    suffix = capi_obfuscate_string("Pageant");
+    pipename = dupprintf("\\\\.\\pipe\\pageant.%s.%s", username, suffix);
+    sfree(username);
+    sfree(suffix);
+    return pipename;
+}
+
+Socket *agent_connect(Plug *plug)
+{
+    char *pipename = agent_named_pipe_name();
+    Socket *s = new_named_pipe_client(pipename, plug);
+    sfree(pipename);
+    return s;
+}
+
+static bool named_pipe_agent_exists(void)
+{
+    char *pipename = agent_named_pipe_name();
+    WIN32_FIND_DATA data;
+    HANDLE ffh = FindFirstFile(pipename, &data);
+    sfree(pipename);
+    if (ffh == INVALID_HANDLE_VALUE)
+        return false;
+    FindClose(ffh);
+    return true;
+}
+
+bool agent_exists(void)
+{
+    return named_pipe_agent_exists() || wm_copydata_agent_exists();
+}
+
+struct agent_pending_query {
+    struct handle *handle;
+    strbuf *response;
+    void (*callback)(void *, void *, int);
+    void *callback_ctx;
+};
+
+static int named_pipe_agent_accumulate_response(
+    strbuf *sb, const void *data, size_t len)
+{
+    put_data(sb, data, len);
+    if (sb->len >= 4) {
+        uint32_t length_field = GET_32BIT_MSB_FIRST(sb->u);
+        if (length_field > AGENT_MAX_MSGLEN)
+            return -1; /* badly formatted message */
+
+        int overall_length = length_field + 4;
+        if (sb->len >= overall_length)
+            return overall_length;
+    }
+
+    return 0; /* not done yet */
+}
+
+static size_t named_pipe_agent_gotdata(
+    struct handle *h, const void *data, size_t len, int err)
+{
+    agent_pending_query *pq = handle_get_privdata(h);
+
+    if (err || len == 0) {
+        pq->callback(pq->callback_ctx, NULL, 0);
+        agent_cancel_query(pq);
+        return 0;
+    }
+
+    int status = named_pipe_agent_accumulate_response(pq->response, data, len);
+    if (status == -1) {
+        pq->callback(pq->callback_ctx, NULL, 0);
+        agent_cancel_query(pq);
+    } else if (status > 0) {
+        void *response_buf = strbuf_to_str(pq->response);
+        pq->response = NULL;
+        pq->callback(pq->callback_ctx, response_buf, status);
+        agent_cancel_query(pq);
+    }
+    return 0;
+}
+
+static agent_pending_query *named_pipe_agent_query(
+    strbuf *query, void **out, int *outlen,
+    void (*callback)(void *, void *, int), void *callback_ctx)
+{
+    agent_pending_query *pq = NULL;
+    char *err = NULL, *pipename = NULL;
+    strbuf *sb = NULL;
+    HANDLE pipehandle;
+
+    pipename = agent_named_pipe_name();
+    pipehandle = connect_to_named_pipe(pipename, &err);
+    if (pipehandle == INVALID_HANDLE_VALUE)
+        goto failure;
+
+    strbuf_finalise_agent_query(query);
+
+    for (DWORD done = 0; done < query->len ;) {
+        DWORD nwritten;
+        bool ret = WriteFile(pipehandle, query->s + done, query->len - done,
+                             &nwritten, NULL);
+        if (!ret)
+            goto failure;
+
+        done += nwritten;
+    }
+
+    if (!callback) {
+        int status;
+
+        sb = strbuf_new_nm();
+        do {
+            char buf[1024];
+            DWORD nread;
+            bool ret = ReadFile(pipehandle, buf, sizeof(buf), &nread, NULL);
+            if (!ret)
+                goto failure;
+            status = named_pipe_agent_accumulate_response(sb, buf, nread);
+        } while (status == 0);
+
+        if (status == -1)
+            goto failure;
+
+        *out = strbuf_to_str(sb);
+        *outlen = status;
+        sb = NULL;
+        pq = NULL;
+        goto out;
+    }
+
+    pq = snew(agent_pending_query);
+    pq->handle = handle_input_new(pipehandle, named_pipe_agent_gotdata, pq, 0);
+    pipehandle = NULL;  /* prevent it being closed below */
+    pq->response = strbuf_new_nm();
+    pq->callback = callback;
+    pq->callback_ctx = callback_ctx;
+    goto out;
+
+  failure:
+    *out = NULL;
+    *outlen = 0;
+    pq = NULL;
+
+  out:
+    sfree(err);
+    sfree(pipename);
+    if (pipehandle != INVALID_HANDLE_VALUE)
+        CloseHandle(pipehandle);
+    if (sb)
+        strbuf_free(sb);
+    return pq;
+}
+
+void agent_cancel_query(agent_pending_query *pq)
+{
+    handle_free(pq->handle);
+    if (pq->response)
+        strbuf_free(pq->response);
+    sfree(pq);
+}
+
+agent_pending_query *agent_query(
+    strbuf *query, void **out, int *outlen,
+    void (*callback)(void *, void *, int), void *callback_ctx)
+{
+    agent_pending_query *pq = named_pipe_agent_query(
+        query, out, outlen, callback, callback_ctx);
+    if (pq || *out)
+        return pq;
+
+    wm_copydata_agent_query(query, out, outlen);
     return NULL;
 }
+
+#else /* NO_SECURITY */
+
+Socket *agent_connect(void *vctx, Plug *plug)
+{
+    unreachable("no agent_connect_ctx can be constructed on this platform");
+}
+
+agent_connect_ctx *agent_get_connect_ctx(void)
+{
+    return NULL;
+}
+
+void agent_free_connect_ctx(agent_connect_ctx *ctx)
+{
+}
+
+bool agent_exists(void)
+{
+    return wm_copydata_agent_exists();
+}
+
+agent_pending_query *agent_query(
+    strbuf *query, void **out, int *outlen,
+    void (*callback)(void *, void *, int), void *callback_ctx)
+{
+    wm_copydata_agent_query(query, out, outlen);
+    return NULL;
+}
+
+void agent_cancel_query(agent_pending_query *q)
+{
+    unreachable("Windows agent queries are never asynchronous!");
+}
+
+#endif /* NO_SECURITY */

+ 1 - 1
source/putty/windows/winproxy.c

@@ -35,7 +35,7 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname,
 
     {
         char *msg = dupprintf("Starting local proxy command: %s", cmd);
-        plug_log(plug, 2, NULL, 0, msg, 0);
+        plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0);
         sfree(msg);
     }
 

+ 13 - 3
source/putty/windows/winsecur.c

@@ -9,12 +9,19 @@
 
 #if !defined NO_SECURITY
 
-#define WINSECUR_GLOBAL
 #include "winsecur.h"
 
 /* Initialised once, then kept around to reuse forever */
 static PSID worldsid, networksid, usersid;
 
+DEF_WINDOWS_FUNCTION(OpenProcessToken);
+DEF_WINDOWS_FUNCTION(GetTokenInformation);
+DEF_WINDOWS_FUNCTION(InitializeSecurityDescriptor);
+DEF_WINDOWS_FUNCTION(SetSecurityDescriptorOwner);
+DEF_WINDOWS_FUNCTION(GetSecurityInfo);
+DEF_WINDOWS_FUNCTION(SetSecurityInfo);
+DEF_WINDOWS_FUNCTION(SetEntriesInAclA);
+
 #ifdef MPEXT
 void win_secur_cleanup(void)
 {
@@ -102,7 +109,7 @@ PSID get_user_sid(void)
     return ret;
 }
 
-bool getsids(char **error)
+static bool getsids(char **error)
 {
 #ifdef __clang__
 #pragma clang diagnostic push
@@ -238,6 +245,9 @@ bool make_private_security_descriptor(DWORD permissions,
     return ret;
 }
 
+static bool acl_restricted = false;
+bool restricted_acl(void) { return acl_restricted; }
+
 static bool really_restrict_process_acl(char **error)
 {
     EXPLICIT_ACCESS ea[2];
@@ -288,7 +298,7 @@ static bool really_restrict_process_acl(char **error)
         goto cleanup;
     }
 
-
+    acl_restricted = true;
     ret=true;
 
   cleanup:

+ 7 - 11
source/putty/windows/winsecur.h

@@ -8,30 +8,26 @@
 
 #include <aclapi.h>
 
-#ifndef WINSECUR_GLOBAL
-#define WINSECUR_GLOBAL extern
-#endif
-
 /*
  * Functions loaded from advapi32.dll.
  */
-DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, BOOL, OpenProcessToken,
+DECL_WINDOWS_FUNCTION(extern, BOOL, OpenProcessToken,
                       (HANDLE, DWORD, PHANDLE));
-DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, BOOL, GetTokenInformation,
+DECL_WINDOWS_FUNCTION(extern, BOOL, GetTokenInformation,
                       (HANDLE, TOKEN_INFORMATION_CLASS,
                        LPVOID, DWORD, PDWORD));
-DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, BOOL, InitializeSecurityDescriptor,
+DECL_WINDOWS_FUNCTION(extern, BOOL, InitializeSecurityDescriptor,
                       (PSECURITY_DESCRIPTOR, DWORD));
-DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, BOOL, SetSecurityDescriptorOwner,
+DECL_WINDOWS_FUNCTION(extern, BOOL, SetSecurityDescriptorOwner,
                       (PSECURITY_DESCRIPTOR, PSID, BOOL));
-DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, DWORD, GetSecurityInfo,
+DECL_WINDOWS_FUNCTION(extern, DWORD, GetSecurityInfo,
                       (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
                        PSID *, PSID *, PACL *, PACL *,
                        PSECURITY_DESCRIPTOR *));
-DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, DWORD, SetSecurityInfo,
+DECL_WINDOWS_FUNCTION(extern, DWORD, SetSecurityInfo,
                       (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
                        PSID, PSID, PACL, PACL));
-DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, DWORD, SetEntriesInAclA,
+DECL_WINDOWS_FUNCTION(extern, DWORD, SetEntriesInAclA,
                       (ULONG, PEXPLICIT_ACCESS, PACL, PACL *));
 bool got_advapi(void);
 

+ 61 - 62
source/putty/windows/winstuff.h

@@ -147,6 +147,9 @@ struct FontSpec *fontspec_new(
 #define DECL_WINDOWS_FUNCTION(linkage, rettype, name, params)   \
     typedef rettype (WINAPI *t_##name) params;                  \
     linkage t_##name p_##name
+/* If you DECL_WINDOWS_FUNCTION as extern in a header file, use this to
+ * define the function pointer in a source file */
+#define DEF_WINDOWS_FUNCTION(name) t_##name p_##name
 #define STR1(x) #x
 #define STR(x) STR1(x)
 #define GET_WINDOWS_FUNCTION_PP(module, name)                           \
@@ -161,19 +164,6 @@ struct FontSpec *fontspec_new(
     (p_##name = module ?                                \
      (t_##name) GetProcAddress(module, #name) : NULL)
 
-/*
- * Global variables. Most modules declare these `extern', but
- * window.c will do `#define PUTTY_DO_GLOBALS' before including this
- * module, and so will get them properly defined.
-*/
-#ifndef GLOBAL
-#ifdef PUTTY_DO_GLOBALS
-#define GLOBAL
-#else
-#define GLOBAL extern
-#endif
-#endif
-
 #define PUTTY_REG_POS "Software\\SimonTatham\\PuTTY"
 #define PUTTY_REG_PARENT "Software\\SimonTatham"
 #define PUTTY_REG_PARENT_CHILD "PuTTY"
@@ -214,16 +204,11 @@ typedef void *Ssh_gss_name;
 #endif
 
 /*
- * Window handles for the windows that can be running during a
- * PuTTY session.
+ * The all-important instance handle, saved from WinMain in every GUI
+ * program and exported for other GUI code to pass back to the Windows
+ * API.
  */
-GLOBAL HWND hwnd;       /* the main terminal window */
-GLOBAL HWND logbox;
-
-/*
- * The all-important instance handle.
- */
-GLOBAL HINSTANCE hinst;
+extern HINSTANCE hinst;
 
 /*
  * Help file stuff in winhelp.c.
@@ -235,23 +220,13 @@ 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 */
 
-/*
- * The terminal and logging context are notionally local to the
- * Windows front end, but they must be shared between window.c and
- * windlg.c. Likewise the Seat structure for the Windows GUI, and the
- * Conf for the main session..
- */
-GLOBAL Terminal *term;
-GLOBAL LogContext *logctx;
-GLOBAL Conf *conf;
-
 /*
  * GUI seat methods in windlg.c, so that the vtable definition in
  * window.c can refer to them.
  */
 int win_seat_verify_ssh_host_key(
-    Seat *seat, const char *host, int port,
-    const char *keytype, char *keystr, char *key_fingerprint,
+    Seat *seat, const char *host, int port, const char *keytype,
+    char *keystr, const char *keydisp, char **key_fingerprints,
     void (*callback)(void *ctx, int result), void *ctx);
 int win_seat_confirm_weak_crypto_primitive(
     Seat *seat, const char *algtype, const char *algname,
@@ -260,12 +235,6 @@ int win_seat_confirm_weak_cached_hostkey(
     Seat *seat, const char *algname, const char *betteralgs,
     void (*callback)(void *ctx, int result), void *ctx);
 
-/*
- * The Windows GUI seat object itself, so that its methods can be
- * called outside window.c.
- */
-extern Seat *const win_seat;
-
 /*
  * Windows-specific clipboard helper function shared with windlg.c,
  * which takes the data string in the system code page instead of
@@ -326,6 +295,8 @@ bool socket_writable(SOCKET skt);
 void socket_reselect_all(void);
 /* Make a SockAddr which just holds a named pipe address. */
 SockAddr *sk_namedpipe_addr(const char *pipename);
+/* Turn a WinSock error code into a string. */
+const char *winsock_error_string(int error);
 
 /*
  * winnet.c dynamically loads WinSock 2 or WinSock 1 depending on
@@ -333,12 +304,12 @@ SockAddr *sk_namedpipe_addr(const char *pipename);
  * that module must be exported from it as function pointers. So
  * here they are.
  */
-DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAAsyncSelect,
+DECL_WINDOWS_FUNCTION(extern, int, WSAAsyncSelect,
                       (SOCKET, HWND, u_int, long));
-DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAEventSelect,
+DECL_WINDOWS_FUNCTION(extern, int, WSAEventSelect,
                       (SOCKET, WSAEVENT, long));
-DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAGetLastError, (void));
-DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAEnumNetworkEvents,
+DECL_WINDOWS_FUNCTION(extern, int, WSAGetLastError, (void));
+DECL_WINDOWS_FUNCTION(extern, int, WSAEnumNetworkEvents,
                       (SOCKET, WSAEVENT, LPWSANETWORKEVENTS));
 #ifdef NEED_DECLARATION_OF_SELECT
 /* This declaration is protected by an ifdef for the sake of building
@@ -348,16 +319,27 @@ DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAEnumNetworkEvents,
  * only a modules actually needing to use (or define, or initialise)
  * this function pointer will see its declaration, and _those_ modules
  * - which will be Windows-specific anyway - can take more care. */
-DECL_WINDOWS_FUNCTION(GLOBAL, int, select,
+DECL_WINDOWS_FUNCTION(extern, int, select,
                       (int, fd_set FAR *, fd_set FAR *,
                        fd_set FAR *, const struct timeval FAR *));
 #endif
 
 /*
- * Provided by each client of winnet.c, and called by winnet.c to turn
- * on or off WSA*Select for a given socket.
+ * Implemented differently depending on the client of winnet.c, and
+ * called by winnet.c to turn on or off WSA*Select for a given socket.
+ */
+const char *do_select(Plug * plug, SOCKET skt, bool enable); // WINSCP
+
+/*
+ * Exports from winselgui.c and winselcli.c, each of which provides an
+ * implementation of do_select.
  */
-char *do_select(Plug * plug, SOCKET skt, bool enable); // WINSCP
+void winselgui_set_hwnd(HWND hwnd);
+void winselgui_clear_hwnd(void);
+
+void winselcli_setup(void);
+SOCKET winselcli_unique_socket(void);
+extern HANDLE winselcli_event;
 
 /*
  * Network-subsystem-related functions provided in other Windows modules.
@@ -367,6 +349,12 @@ Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H,
 Socket *new_named_pipe_client(const char *pipename, Plug *plug); /* winnpc */
 Socket *new_named_pipe_listener(const char *pipename, Plug *plug); /* winnps */
 
+/* A lower-level function in winnpc.c, which does most of the work of
+ * new_named_pipe_client (including checking the ownership of what
+ * it's connected to), but returns a plain HANDLE instead of wrapping
+ * it into a Socket. */
+HANDLE connect_to_named_pipe(const char *pipename, char **err);
+
 /*
  * Exports from winctrls.c.
  */
@@ -389,7 +377,10 @@ typedef struct filereq_tag filereq; /* cwd for file requester */
 bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save);
 filereq *filereq_new(void);
 void filereq_free(filereq *state);
-int message_box(LPCTSTR text, LPCTSTR caption, DWORD style, DWORD helpctxid);
+void pgp_fingerprints_msgbox(HWND owner);
+int message_box(HWND owner, LPCTSTR text, LPCTSTR caption,
+                DWORD style, DWORD helpctxid);
+void MakeDlgItemBorderless(HWND parent, int id);
 char *GetDlgItemText_alloc(HWND hwnd, int id);
 void split_into_argv(char *, int *, char ***, char ***);
 
@@ -507,6 +498,12 @@ struct winctrl {
      */
     int base_id;
     int num_ids;
+    /*
+     * For vertical alignment, the id of a particular representative
+     * control that has the y-extent of the sensible part of the
+     * control.
+     */
+    int align_id;
     /*
      * Remember what keyboard shortcuts were used by this control,
      * so that when we remove it again we can take them out of the
@@ -558,24 +555,25 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help,
  * Exports from windlg.c.
  */
 void defuse_showwindow(void);
-bool do_config(void);
-bool do_reconfig(HWND, int);
+bool do_config(Conf *);
+bool do_reconfig(HWND, Conf *, int);
 void showeventlog(HWND);
 void showabout(HWND);
 void force_normal(HWND hwnd);
 void modal_about_box(HWND hwnd);
 void show_help(HWND hwnd);
+HWND event_log_window(void);
 
 /*
  * Exports from winmisc.c.
  */
-GLOBAL DWORD osMajorVersion, osMinorVersion, osPlatformId;
+extern DWORD osMajorVersion, osMinorVersion, osPlatformId;
 void init_winver(void);
 void dll_hijacking_protection(void);
 HMODULE load_system32_dll(const char *libname);
 const char *win_strerror(int error);
 void restrict_process_acl(void);
-GLOBAL bool restricted_acl;
+bool restricted_acl(void);
 void escape_registry_key(const char *in, strbuf *out);
 void unescape_registry_key(const char *in, strbuf *out);
 
@@ -646,16 +644,9 @@ struct handle_sink {
 void handle_sink_init(handle_sink *sink, struct handle *h);
 
 /*
- * winpgntc.c needs to schedule callbacks for asynchronous agent
- * requests. This has to be done differently in GUI and console, so
- * there's an exported function used for the purpose.
- *
- * Also, we supply FLAG_SYNCAGENT to force agent requests to be
- * synchronous in pscp and psftp.
+ * Exports from winpgntc.c.
  */
-void agent_schedule_callback(void (*callback)(void *, void *, int),
-                             void *callback_ctx, void *data, int len);
-#define FLAG_SYNCAGENT 0x1000
+char *agent_named_pipe_name(void);
 
 /*
  * Exports from winser.c.
@@ -713,4 +704,12 @@ char *get_jumplist_registry_entries(void);
 /* In winmisc.c */
 char *registry_get_string(HKEY root, const char *path, const char *leaf);
 
+/* In wincliloop.c */
+typedef bool (*cliloop_pre_t)(void *vctx, const HANDLE **extra_handles,
+                              size_t *n_extra_handles);
+typedef bool (*cliloop_post_t)(void *vctx, size_t extra_handle_index);
+void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx);
+bool cliloop_null_pre(void *vctx, const HANDLE **, size_t *);
+bool cliloop_null_post(void *vctx, size_t);
+
 #endif

+ 28 - 29
source/putty/x11fwd.c

@@ -688,7 +688,7 @@ void x11_format_auth_for_authfile(
     put_stringpl_xauth(bs, authdata);
 }
 
-static void x11_log(Plug *p, int type, SockAddr *addr, int port,
+static void x11_log(Plug *p, PlugLogType type, SockAddr *addr, int port,
                     const char *error_msg, int error_code)
 {
     /* We have no interface to the logging module here, so we drop these. */
@@ -766,11 +766,10 @@ int x11_get_screen_number(char *display)
 }
 
 static const PlugVtable X11Connection_plugvt = {
-    x11_log,
-    x11_closing,
-    x11_receive,
-    x11_sent,
-    NULL
+    .log = x11_log,
+    .closing = x11_closing,
+    .receive = x11_receive,
+    .sent = x11_sent,
 };
 
 static void x11_chan_free(Channel *chan);
@@ -780,29 +779,29 @@ static void x11_send_eof(Channel *chan);
 static void x11_set_input_wanted(Channel *chan, bool wanted);
 static char *x11_log_close_msg(Channel *chan);
 
-static const struct ChannelVtable X11Connection_channelvt = {
-    x11_chan_free,
-    chan_remotely_opened_confirmation,
-    chan_remotely_opened_failure,
-    x11_send,
-    x11_send_eof,
-    x11_set_input_wanted,
-    x11_log_close_msg,
-    chan_default_want_close,
-    chan_no_exit_status,
-    chan_no_exit_signal,
-    chan_no_exit_signal_numeric,
-    chan_no_run_shell,
-    chan_no_run_command,
-    chan_no_run_subsystem,
-    chan_no_enable_x11_forwarding,
-    chan_no_enable_agent_forwarding,
-    chan_no_allocate_pty,
-    chan_no_set_env,
-    chan_no_send_break,
-    chan_no_send_signal,
-    chan_no_change_window_size,
-    chan_no_request_response,
+static const ChannelVtable X11Connection_channelvt = {
+    .free = x11_chan_free,
+    .open_confirmation = chan_remotely_opened_confirmation,
+    .open_failed = chan_remotely_opened_failure,
+    .send = x11_send,
+    .send_eof = x11_send_eof,
+    .set_input_wanted = x11_set_input_wanted,
+    .log_close_msg = x11_log_close_msg,
+    .want_close = chan_default_want_close,
+    .rcvd_exit_status = chan_no_exit_status,
+    .rcvd_exit_signal = chan_no_exit_signal,
+    .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
+    .run_shell = chan_no_run_shell,
+    .run_command = chan_no_run_command,
+    .run_subsystem = chan_no_run_subsystem,
+    .enable_x11_forwarding = chan_no_enable_x11_forwarding,
+    .enable_agent_forwarding = chan_no_enable_agent_forwarding,
+    .allocate_pty = chan_no_allocate_pty,
+    .set_env = chan_no_set_env,
+    .send_break = chan_no_send_break,
+    .send_signal = chan_no_send_signal,
+    .change_window_size = chan_no_change_window_size,
+    .request_response = chan_no_request_response,
 };
 
 /*

Деякі файли не було показано, через те що забагато файлів було змінено