Bläddra i källkod

PuTTY 0.75

(cherry picked from commit d7a42c81692e90527135b88e4ee8207d9805c19c)

Source commit: c82fe372e18d04bc35d4e07990a22727b354eb07
Martin Prikryl 4 år sedan
förälder
incheckning
0e0239f51b
100 ändrade filer med 7297 tillägg och 2550 borttagningar
  1. 93 0
      source/putty/WINDOWS/wincapi.c
  2. 31 0
      source/putty/WINDOWS/wincapi.h
  3. 9 9
      source/putty/WINDOWS/winhsock.c
  4. 8 0
      source/putty/WINDOWS/winmiscs.c
  5. 98 0
      source/putty/WINDOWS/winnpc.c
  6. 12 3
      source/putty/WINDOWS/winsecur.c
  7. 7 11
      source/putty/WINDOWS/winsecur.h
  8. 23 23
      source/putty/agentf.c
  9. 22 19
      source/putty/be_misc.c
  10. 6 4
      source/putty/be_ssh.c
  11. 3 3
      source/putty/callback.c
  12. 5 0
      source/putty/defs.h
  13. 12 9
      source/putty/doc/Makefile
  14. 259 145
      source/putty/doc/config.but
  15. 1 1
      source/putty/doc/copy.but
  16. 2 2
      source/putty/doc/errors.but
  17. 12 2
      source/putty/doc/faq.but
  18. 1 1
      source/putty/doc/feedback.but
  19. 41 26
      source/putty/doc/gs.but
  20. 44 11
      source/putty/doc/index.but
  21. 28 26
      source/putty/doc/intro.but
  22. 2 2
      source/putty/doc/licence.but
  23. 146 35
      source/putty/doc/man-pageant.but
  24. 20 2
      source/putty/doc/man-plink.but
  25. 26 2
      source/putty/doc/man-pscp.but
  26. 26 2
      source/putty/doc/man-psftp.but
  27. 92 0
      source/putty/doc/man-psocks.but
  28. 375 0
      source/putty/doc/man-psusan.but
  29. 0 0
      source/putty/doc/man-pterm.but
  30. 18 6
      source/putty/doc/man-putty.but
  31. 144 15
      source/putty/doc/man-puttygen.but
  32. 5 5
      source/putty/doc/man-puttytel.but
  33. 72 4
      source/putty/doc/pageant.but
  34. 25 18
      source/putty/doc/plink.but
  35. 8 3
      source/putty/doc/pscp.but
  36. 124 8
      source/putty/doc/pubkey.but
  37. 408 0
      source/putty/doc/pubkeyfmt.but
  38. 63 0
      source/putty/doc/sshnames.but
  39. 385 6
      source/putty/doc/udp.but
  40. 108 35
      source/putty/doc/using.but
  41. 8 3
      source/putty/ecc.c
  42. 5 0
      source/putty/ecc.h
  43. 7 10
      source/putty/errsock.c
  44. 97 91
      source/putty/import.c
  45. 1 1
      source/putty/logging.c
  46. 24 25
      source/putty/mainchan.c
  47. 47 0
      source/putty/marshal.c
  48. 9 0
      source/putty/marshal.h
  49. 54 12
      source/putty/misc.c
  50. 37 0
      source/putty/misc.h
  51. 333 109
      source/putty/mpint.c
  52. 44 2
      source/putty/mpint.h
  53. 3 0
      source/putty/mpint_i.h
  54. 29 13
      source/putty/network.h
  55. 1 1
      source/putty/noshare.c
  56. 6 7
      source/putty/nullplug.c
  57. 150 39
      source/putty/pageant.h
  58. 39 42
      source/putty/portfwd.c
  59. 72 76
      source/putty/proxy.c
  60. 343 140
      source/putty/putty.h
  61. 28 8
      source/putty/settings.c
  62. 104 51
      source/putty/ssh.c
  63. 193 66
      source/putty/ssh.h
  64. 7 7
      source/putty/ssh1bpp.c
  65. 45 31
      source/putty/ssh1connection-client.c
  66. 36 66
      source/putty/ssh1connection.c
  67. 0 4
      source/putty/ssh1connection.h
  68. 32 32
      source/putty/ssh1login.c
  69. 21 7
      source/putty/ssh2bpp-bare.c
  70. 64 7
      source/putty/ssh2bpp.c
  71. 23 3
      source/putty/ssh2connection-client.c
  72. 71 72
      source/putty/ssh2connection.c
  73. 1 3
      source/putty/ssh2connection.h
  74. 33 21
      source/putty/ssh2kex-client.c
  75. 163 25
      source/putty/ssh2transport.c
  76. 7 1
      source/putty/ssh2transport.h
  77. 60 32
      source/putty/ssh2userauth.c
  78. 46 17
      source/putty/sshaes.c
  79. 24 10
      source/putty/ssharcf.c
  80. 593 0
      source/putty/sshargon2.c
  81. 5 7
      source/putty/sshauxcrypt.c
  82. 242 0
      source/putty/sshblake2.c
  83. 35 18
      source/putty/sshblowf.c
  84. 5 0
      source/putty/sshbpp.h
  85. 26 19
      source/putty/sshccp.c
  86. 4 0
      source/putty/sshchan.h
  87. 41 158
      source/putty/sshcommon.c
  88. 7 5
      source/putty/sshcr.h
  89. 2 2
      source/putty/sshcrcda.c
  90. 60 16
      source/putty/sshdes.c
  91. 34 21
      source/putty/sshdss.c
  92. 272 123
      source/putty/sshecc.c
  93. 78 65
      source/putty/sshhmac.c
  94. 190 220
      source/putty/sshmd5.c
  95. 1 0
      source/putty/sshppl.h
  96. 30 35
      source/putty/sshprng.c
  97. 448 298
      source/putty/sshpubk.c
  98. 3 3
      source/putty/sshrand.c
  99. 108 37
      source/putty/sshrsa.c
  100. 82 51
      source/putty/sshsh256.c

+ 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

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

@@ -272,7 +272,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
@@ -304,14 +304,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,

+ 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)

+ 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 */

+ 12 - 3
source/putty/WINDOWS/winsecur.c

@@ -9,12 +9,18 @@
 
 #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);
 
 bool got_advapi(void)
 {
@@ -92,7 +98,7 @@ PSID get_user_sid(void)
     return ret;
 }
 
-bool getsids(char **error)
+static bool getsids(char **error)
 {
 #ifdef __clang__
 #pragma clang diagnostic push
@@ -228,6 +234,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];
@@ -278,7 +287,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);
 

+ 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;
@@ -114,7 +117,7 @@ void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb,
                 endpos--;
             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;
@@ -129,7 +132,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

@@ -14,10 +14,10 @@ struct callback {
     void *ctx;
 };
 
-struct callback *cbcurr = NULL, *cbhead = NULL, *cbtail = NULL;
+static struct callback *cbcurr = NULL, *cbhead = NULL, *cbtail = NULL;
 
-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 _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"
@@ -64,6 +66,7 @@ typedef struct FontSpec FontSpec;
 typedef struct bufchain_tag bufchain;
 
 typedef struct strbuf strbuf;
+typedef struct LoadedFile LoadedFile;
 
 typedef struct RSAKey RSAKey;
 
@@ -181,6 +184,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

+ 12 - 9
source/putty/doc/Makefile

@@ -35,7 +35,8 @@ VERSIONIDS=vids
 endif
 
 CHAPTERS := $(SITE) copy blurb intro gs using config pscp psftp plink
-CHAPTERS += pubkey pageant errors faq feedback licence udp pgpkeys sshnames
+CHAPTERS += pubkey pageant errors faq feedback pubkeyfmt licence udp
+CHAPTERS += pgpkeys sshnames
 CHAPTERS += index $(VERSIONIDS)
 
 INPUTS = $(patsubst %,%.but,$(CHAPTERS))
@@ -59,17 +60,19 @@ putty.info: $(INPUTS)
 
 MKMAN = $(HALIBUT) --man=$@ mancfg.but $<
 MANPAGES = putty.1 puttygen.1 plink.1 pscp.1 psftp.1 puttytel.1 pterm.1 \
-           pageant.1
+           pageant.1 psocks.1 psusan.1
 man: $(MANPAGES)
 
-putty.1: man-putt.but mancfg.but; $(MKMAN)
-puttygen.1: man-pg.but mancfg.but; $(MKMAN)
-plink.1: man-pl.but mancfg.but; $(MKMAN)
+putty.1: man-putty.but mancfg.but; $(MKMAN)
+puttygen.1: man-puttygen.but mancfg.but; $(MKMAN)
+plink.1: man-plink.but mancfg.but; $(MKMAN)
 pscp.1: man-pscp.but mancfg.but; $(MKMAN)
-psftp.1: man-psft.but mancfg.but; $(MKMAN)
-puttytel.1: man-ptel.but mancfg.but; $(MKMAN)
-pterm.1: man-pter.but mancfg.but; $(MKMAN)
-pageant.1: man-pag.but mancfg.but; $(MKMAN)
+psftp.1: man-psftp.but mancfg.but; $(MKMAN)
+puttytel.1: man-puttytel.but mancfg.but; $(MKMAN)
+pterm.1: man-pterm.but mancfg.but; $(MKMAN)
+pageant.1: man-pageant.but mancfg.but; $(MKMAN)
+psocks.1: man-psocks.but mancfg.but; $(MKMAN)
+psusan.1: man-psusan.but mancfg.but; $(MKMAN)
 
 mostlyclean:
 	rm -f *.html *.txt *.hlp *.cnt *.1 *.info vstr.but *.hh[pck]

+ 259 - 145
source/putty/doc/config.but

@@ -14,28 +14,47 @@ save your settings to be reloaded later.
 
 \S{config-hostname} The \i{host name} section
 
-The top box on the Session panel, labelled \q{Specify your
-connection by host name}, contains the details that need to be
-filled in before PuTTY can open a session at all.
+The top box on the Session panel, labelled \q{Specify the destination
+you want to connect to}, contains the details that need to be filled
+in before PuTTY can open a session at all.
 
 \b The \q{Host Name} box is where you type the name, or the \i{IP
 address}, of the server you want to connect to.
 
-\b The \q{Connection type} radio buttons let you choose what type of
-connection you want to make: a \I{raw TCP connections}raw
-connection, a \i{Telnet} connection, an \i{Rlogin} connection, an
-\i{SSH} connection, or a connection to a local \i{serial line}. (See
-\k{which-one} for a summary of the differences between SSH, Telnet
-and rlogin; see \k{using-rawprot} for an explanation of \q{raw}
-connections; see \k{using-serial} for information about using a
-serial line.)
+\b The \q{Connection type} controls let you choose what type of
+connection you want to make: an \i{SSH} network connection, a
+connection to a local \i{serial line}, or various other kinds of
+network connection.
+
+\lcont{
+
+\b See \k{which-one} for a summary of the
+differences between the network remote login protocols SSH, Telnet,
+Rlogin, and SUPDUP.
+
+\b See \k{using-serial} for information about using a serial line.
+
+\b See \k{using-rawprot} for an explanation of \q{raw}
+connections.
+
+\b See \k{using-telnet} for a little information about Telnet.
+
+\b See \k{using-rlogin} for information about using Rlogin.
+
+\b See \k{using-supdup} for information about using SUPDUP.
+
+\b The \q{Bare ssh-connection} option in the \q{Connection type}
+control is intended for specialist uses not involving network
+connections. See \k{config-psusan} for some information about it.
+
+}
 
 \b The \q{Port} box lets you specify which \i{port number} on the
-server to connect to. If you select Telnet, Rlogin, or SSH, this box
-will be filled in automatically to the usual value, and you will
-only need to change it if you have an unusual server. If you select
-Raw mode, you will almost certainly need to fill in the \q{Port} box
-yourself.
+server to connect to. If you select Telnet, Rlogin, SUPDUP, or SSH,
+this box will be filled in automatically to the usual value, and you
+will only need to change it if you have an unusual server. If you
+select Raw mode, you will almost certainly need to fill in the
+\q{Port} box yourself.
 
 If you select \q{Serial} from the \q{Connection type} radio buttons,
 the \q{Host Name} and \q{Port} boxes are replaced by \q{Serial line}
@@ -1668,8 +1687,8 @@ connection loss, or you might find they make it worse, depending on
 what \e{kind} of network problems you have between you and the
 server.
 
-Keepalives are only supported in Telnet and SSH; the Rlogin and Raw
-protocols offer no way of implementing them. (For an alternative, see
+Keepalives are only supported in Telnet and SSH; the Rlogin, SUPDUP, and
+Raw protocols offer no way of implementing them. (For an alternative, see
 \k{config-tcp-keepalives}.)
 
 Note that if you are using SSH-1 and the server has a bug that makes
@@ -1697,8 +1716,8 @@ are provided for completeness.
 The idea of TCP keepalives is similar to application-level keepalives,
 and the same caveats apply. The main differences are:
 
-\b TCP keepalives are available on \e{all} connection types, including
-Raw and Rlogin.
+\b TCP keepalives are available on \e{all} network connection types,
+including Raw, Rlogin, and SUPDUP.
 
 \b The interval between TCP keepalives is usually much longer,
 typically two hours; this is set by the operating system, and cannot
@@ -1791,7 +1810,7 @@ configuration panels.
 
 \S{config-username} \q{\ii{Auto-login username}}
 
-All three of the SSH, Telnet and Rlogin protocols allow you to
+All three of the SSH, Telnet, and Rlogin protocols allow you to
 specify what user name you want to log in as, without having to type
 it explicitly every time. (Some Telnet servers don't support this.)
 
@@ -1820,7 +1839,7 @@ Most servers you might connect to with PuTTY are designed to be
 connected to from lots of different types of terminal. In order to
 send the right \i{control sequence}s to each one, the server will need
 to know what type of terminal it is dealing with. Therefore, each of
-the SSH, Telnet and Rlogin protocols allow a text string to be sent
+the SSH, Telnet, and Rlogin protocols allow a text string to be sent
 down the connection describing the terminal.  On a \i{Unix} server,
 this selects an entry from the \i\c{termcap} or \i\c{terminfo} database
 that tells applications what \i{control sequences} to send to the
@@ -2087,123 +2106,6 @@ session is deemed to have started (in a protocol-dependent way), which
 is when they're most likely to be interesting; any further proxy-related
 messages during the session will only go to the Event Log.
 
-\H{config-telnet} The \i{Telnet} panel
-
-The Telnet panel allows you to configure options that only apply to
-Telnet sessions.
-
-\S{config-oldenviron} \q{Handling of OLD_ENVIRON ambiguity}
-
-The original Telnet mechanism for passing \i{environment variables} was
-badly specified. At the time the standard (RFC 1408) was written,
-BSD telnet implementations were already supporting the feature, and
-the intention of the standard was to describe the behaviour the BSD
-implementations were already using.
-
-Sadly there was a typing error in the standard when it was issued,
-and two vital function codes were specified the wrong way round. BSD
-implementations did not change, and the standard was not corrected.
-Therefore, it's possible you might find either \i{BSD} or \i{RFC}-compliant
-implementations out there. This switch allows you to choose which
-one PuTTY claims to be.
-
-The problem was solved by issuing a second standard, defining a new
-Telnet mechanism called \i\cw{NEW_ENVIRON}, which behaved exactly like
-the original \i\cw{OLD_ENVIRON} but was not encumbered by existing
-implementations. Most Telnet servers now support this, and it's
-unambiguous. This feature should only be needed if you have trouble
-passing environment variables to quite an old server.
-
-\S{config-ptelnet} Passive and active \i{Telnet negotiation} modes
-
-In a Telnet connection, there are two types of data passed between
-the client and the server: actual text, and \e{negotiations} about
-which Telnet extra features to use.
-
-PuTTY can use two different strategies for negotiation:
-
-\b In \I{active Telnet negotiation}\e{active} mode, PuTTY starts to send
-negotiations as soon as the connection is opened.
-
-\b In \I{passive Telnet negotiation}\e{passive} mode, PuTTY will wait to
-negotiate until it sees a negotiation from the server.
-
-The obvious disadvantage of passive mode is that if the server is
-also operating in a passive mode, then negotiation will never begin
-at all. For this reason PuTTY defaults to active mode.
-
-However, sometimes passive mode is required in order to successfully
-get through certain types of firewall and \i{Telnet proxy} server. If
-you have confusing trouble with a \i{firewall}, you could try enabling
-passive mode to see if it helps.
-
-\S{config-telnetkey} \q{Keyboard sends \i{Telnet special commands}}
-
-If this box is checked, several key sequences will have their normal
-actions modified:
-
-\b the Backspace key on the keyboard will send the \I{Erase Character,
-Telnet special command}Telnet special backspace code;
-
-\b Control-C will send the Telnet special \I{Interrupt Process, Telnet
-special command}Interrupt Process code;
-
-\b Control-Z will send the Telnet special \I{Suspend Process, Telnet
-special command}Suspend Process code.
-
-You probably shouldn't enable this
-unless you know what you're doing.
-
-\S{config-telnetnl} \q{Return key sends \i{Telnet New Line} instead of ^M}
-
-Unlike most other remote login protocols, the Telnet protocol has a
-special \q{\i{new line}} code that is not the same as the usual line
-endings of Control-M or Control-J. By default, PuTTY sends the
-Telnet New Line code when you press Return, instead of sending
-Control-M as it does in most other protocols.
-
-Most Unix-style Telnet servers don't mind whether they receive
-Telnet New Line or Control-M; some servers do expect New Line, and
-some servers prefer to see ^M. If you are seeing surprising
-behaviour when you press Return in a Telnet session, you might try
-turning this option off to see if it helps.
-
-\H{config-rlogin} The Rlogin panel
-
-The \i{Rlogin} panel allows you to configure options that only apply to
-Rlogin sessions.
-
-\S{config-rlogin-localuser} \I{local username in Rlogin}\q{Local username}
-
-Rlogin allows an automated (password-free) form of login by means of
-a file called \i\c{.rhosts} on the server. You put a line in your
-\c{.rhosts} file saying something like \c{[email protected]},
-and then when you make an Rlogin connection the client transmits the
-username of the user running the Rlogin client. The server checks
-the username and hostname against \c{.rhosts}, and if they match it
-\I{passwordless login}does not ask for a password.
-
-This only works because Unix systems contain a safeguard to stop a
-user from pretending to be another user in an Rlogin connection.
-Rlogin connections have to come from \I{privileged port}port numbers below
-1024, and Unix systems prohibit this to unprivileged processes; so when the
-server sees a connection from a low-numbered port, it assumes the
-client end of the connection is held by a privileged (and therefore
-trusted) process, so it believes the claim of who the user is.
-
-Windows does not have this restriction: \e{any} user can initiate an
-outgoing connection from a low-numbered port. Hence, the Rlogin
-\c{.rhosts} mechanism is completely useless for securely
-distinguishing several different users on a Windows machine. If you
-have a \c{.rhosts} entry pointing at a Windows PC, you should assume
-that \e{anyone} using that PC can \i{spoof} your username in
-an Rlogin connection and access your account on the server.
-
-The \q{Local username} control allows you to specify what user name
-PuTTY should claim you have, in case it doesn't match your \i{Windows
-user name} (or in case you didn't bother to set up a Windows user
-name).
-
 \H{config-ssh} The SSH panel
 
 The \i{SSH} panel allows you to configure options that only apply to
@@ -2521,9 +2423,13 @@ Configuration is similar to cipher selection (see
 
 PuTTY currently supports the following host key types:
 
-\b \q{Ed25519}: \i{Edwards-curve} \i{DSA} using a twisted Edwards
+\b \q{\i{Ed25519}}: \I{EdDSA}Edwards-curve DSA using a twisted Edwards
 curve with modulus \cw{2^255-19}.
 
+\b \q{\i{Ed448}}: another \I{EdDSA}Edwards-curve DSA type, using a
+larger elliptic curve with a 448-bit instead of 255-bit modulus (so it
+has a higher security level than Ed25519).
+
 \b \q{ECDSA}: \i{elliptic curve} \i{DSA} using one of the
 NIST-standardised elliptic curves.
 
@@ -2532,8 +2438,8 @@ NIST-standardised elliptic curves.
 \b \q{RSA}: the ordinary \i{RSA} algorithm.
 
 If PuTTY already has one or more host keys stored for the server,
-it will prefer to use one of those, even if the server has a key
-type that is higher in the preference order. You can add such a
+it will by default prefer to use one of those, even if the server has
+a key type that is higher in the preference order. You can add such a
 key to PuTTY's cache from within an existing session using the
 \q{Special Commands} menu; see \k{using-specials}.
 
@@ -2598,9 +2504,13 @@ You can remove keys again with the \q{Remove} button.
 
 The text describing a host key can be in one of the following formats:
 
-\b An MD5-based host key fingerprint of the form displayed in PuTTY's
-Event Log and host key dialog boxes, i.e. sixteen 2-digit hex numbers
-separated by colons.
+\b An \I{SHA256 fingerprint}SHA-256-based host key fingerprint of the
+form displayed in PuTTY's Event Log and host key dialog boxes,
+i.e. \cq{SHA256:} followed by 43 case-sensitive characters.
+
+\b An \I{MD5 fingerprint}MD5-based host key fingerprint, i.e. sixteen
+2-digit hex numbers separated by colons, optionally preceded by the
+prefix \cq{MD5:}. (The case of the characters does not matter.)
 
 \b A base64-encoded blob describing an SSH-2 public key in
 OpenSSH's one-line public key format. How you acquire a public key in
@@ -3479,6 +3389,52 @@ will be impossible.
 
 This is an SSH-1-specific bug.
 
+\H{config-psusan} The \q{Bare \cw{\i{ssh-connection}}} protocol
+
+In addition to SSH itself, PuTTY also supports a second protocol that
+is derived from SSH. It's listed in the PuTTY GUI under the name
+\q{Bare \cw{ssh-connection}}.
+
+This protocol consists of just the innermost of SSH-2's three layers: it
+leaves out the cryptography layer providing network security, and it
+leaves out the authentication layer where you provide a username and
+prove you're allowed to log in as that user.
+
+It is therefore \s{completely unsuited to any network connection}.
+Don't try to use it over a network!
+
+The purpose of this protocol is for various specialist circumstances
+in which the \q{connection} is not over a real network, but is a pipe
+or IPC channel between different processes running on the \e{same}
+computer. In these contexts, the operating system will already have
+guaranteed that each of the two communicating processes is owned by
+the expected user (so that no authentication is necessary), and that
+the communications channel cannot be tapped by a hostile user on the
+same machine (so that no cryptography is necessary either). Examples
+of possible uses involve communicating with a strongly separated
+context such as the inside of a container, or a VM, or a different
+network namespace.
+
+Explicit support for this protocol is new in PuTTY 0.75. As of
+2021-04, the only known server for the bare \cw{ssh-connection}
+protocol is the Unix program \cq{\i{psusan}} that is also part of the
+PuTTY tool suite.
+
+(However, this protocol is also the same one used between instances of
+PuTTY to implement connection sharing: see \k{config-ssh-sharing}. In
+fact, in the Unix version of PuTTY, when a sharing upstream records
+\q{Sharing this connection at [pathname]} in the Event Log, it's
+possible to connect another instance of PuTTY directly to that Unix
+socket, by entering its pathname in the host name box and selecting
+\q{Bare \cw{ssh-connection}} as the protocol!)
+
+Many of the options under the SSH panel also affect this protocol,
+although options to do with cryptography and authentication do not,
+for obvious reasons.
+
+I repeat, \s{DON'T TRY TO USE THIS PROTOCOL FOR NETWORK CONNECTIONS!}
+That's not what it's for, and it's not at all safe to do it.
+
 \H{config-serial} The Serial panel
 
 The \i{Serial} panel allows you to configure options that only apply
@@ -3556,6 +3512,164 @@ the serial line.
 \b \q{DSR/DTR}: flow control is done using the DSR and DTR wires on
 the serial line.
 
+\H{config-telnet} The \i{Telnet} panel
+
+The Telnet panel allows you to configure options that only apply to
+Telnet sessions.
+
+\S{config-oldenviron} \q{Handling of OLD_ENVIRON ambiguity}
+
+The original Telnet mechanism for passing \i{environment variables} was
+badly specified. At the time the standard (RFC 1408) was written,
+BSD telnet implementations were already supporting the feature, and
+the intention of the standard was to describe the behaviour the BSD
+implementations were already using.
+
+Sadly there was a typing error in the standard when it was issued,
+and two vital function codes were specified the wrong way round. BSD
+implementations did not change, and the standard was not corrected.
+Therefore, it's possible you might find either \i{BSD} or \i{RFC}-compliant
+implementations out there. This switch allows you to choose which
+one PuTTY claims to be.
+
+The problem was solved by issuing a second standard, defining a new
+Telnet mechanism called \i\cw{NEW_ENVIRON}, which behaved exactly like
+the original \i\cw{OLD_ENVIRON} but was not encumbered by existing
+implementations. Most Telnet servers now support this, and it's
+unambiguous. This feature should only be needed if you have trouble
+passing environment variables to quite an old server.
+
+\S{config-ptelnet} Passive and active \i{Telnet negotiation} modes
+
+In a Telnet connection, there are two types of data passed between
+the client and the server: actual text, and \e{negotiations} about
+which Telnet extra features to use.
+
+PuTTY can use two different strategies for negotiation:
+
+\b In \I{active Telnet negotiation}\e{active} mode, PuTTY starts to send
+negotiations as soon as the connection is opened.
+
+\b In \I{passive Telnet negotiation}\e{passive} mode, PuTTY will wait to
+negotiate until it sees a negotiation from the server.
+
+The obvious disadvantage of passive mode is that if the server is
+also operating in a passive mode, then negotiation will never begin
+at all. For this reason PuTTY defaults to active mode.
+
+However, sometimes passive mode is required in order to successfully
+get through certain types of firewall and \i{Telnet proxy} server. If
+you have confusing trouble with a \i{firewall}, you could try enabling
+passive mode to see if it helps.
+
+\S{config-telnetkey} \q{Keyboard sends \i{Telnet special commands}}
+
+If this box is checked, several key sequences will have their normal
+actions modified:
+
+\b the Backspace key on the keyboard will send the \I{Erase Character,
+Telnet special command}Telnet special backspace code;
+
+\b Control-C will send the Telnet special \I{Interrupt Process, Telnet
+special command}Interrupt Process code;
+
+\b Control-Z will send the Telnet special \I{Suspend Process, Telnet
+special command}Suspend Process code.
+
+You probably shouldn't enable this
+unless you know what you're doing.
+
+\S{config-telnetnl} \q{Return key sends \i{Telnet New Line} instead of ^M}
+
+Unlike most other remote login protocols, the Telnet protocol has a
+special \q{\i{new line}} code that is not the same as the usual line
+endings of Control-M or Control-J. By default, PuTTY sends the
+Telnet New Line code when you press Return, instead of sending
+Control-M as it does in most other protocols.
+
+Most Unix-style Telnet servers don't mind whether they receive
+Telnet New Line or Control-M; some servers do expect New Line, and
+some servers prefer to see ^M. If you are seeing surprising
+behaviour when you press Return in a Telnet session, you might try
+turning this option off to see if it helps.
+
+\H{config-rlogin} The Rlogin panel
+
+The \i{Rlogin} panel allows you to configure options that only apply to
+Rlogin sessions.
+
+\S{config-rlogin-localuser} \I{local username in Rlogin}\q{Local username}
+
+Rlogin allows an automated (password-free) form of login by means of
+a file called \i\c{.rhosts} on the server. You put a line in your
+\c{.rhosts} file saying something like \c{[email protected]},
+and then when you make an Rlogin connection the client transmits the
+username of the user running the Rlogin client. The server checks
+the username and hostname against \c{.rhosts}, and if they match it
+\I{passwordless login}does not ask for a password.
+
+This only works because Unix systems contain a safeguard to stop a
+user from pretending to be another user in an Rlogin connection.
+Rlogin connections have to come from \I{privileged port}port numbers below
+1024, and Unix systems prohibit this to unprivileged processes; so when the
+server sees a connection from a low-numbered port, it assumes the
+client end of the connection is held by a privileged (and therefore
+trusted) process, so it believes the claim of who the user is.
+
+Windows does not have this restriction: \e{any} user can initiate an
+outgoing connection from a low-numbered port. Hence, the Rlogin
+\c{.rhosts} mechanism is completely useless for securely
+distinguishing several different users on a Windows machine. If you
+have a \c{.rhosts} entry pointing at a Windows PC, you should assume
+that \e{anyone} using that PC can \i{spoof} your username in
+an Rlogin connection and access your account on the server.
+
+The \q{Local username} control allows you to specify what user name
+PuTTY should claim you have, in case it doesn't match your \i{Windows
+user name} (or in case you didn't bother to set up a Windows user
+name).
+
+\H{config-supdup} The \i{SUPDUP} panel
+
+The SUPDUP panel allows you to configure options that only apply
+to SUPDUP sessions. See \k{using-supdup} for more about the SUPDUP
+protocol.
+
+\S{supdup-location} \q{Location string}
+
+In SUPDUP, the client sends a piece of text of its choice to the
+server giving the user's location. This is typically displayed in
+lists of logged-in users.
+
+By default, PuTTY just defaults this to "The Internet". If you want
+your location to show up as something more specific, you can configure
+it here.
+
+\S{supdup-ascii} \q{Extended ASCII Character set}
+
+This declares what kind of character set extension your terminal
+supports. If the server supports it, it will send text using that
+character set. \q{None} means the standard 95 printable ASCII
+characters. \q{ITS} means ASCII extended with printable characters in
+the control character range. This character set is documented in the
+SUPDUP protocol definition. \q{WAITS} is similar to \q{ITS} but uses
+some alternative characters in the extended set: most prominently, it
+will display arrows instead of \c{^} and \c{_}, and \c{\}} instead of
+\c{~}.  \q{ITS} extended ASCII is used by ITS and Lisp machines,
+whilst \q{WAITS} is only used by the WAITS operating system from the
+Stanford AI Laboratory.
+
+\S{supdup-more} \q{**MORE** processing}
+
+When **MORE** processing is enabled, the server causes output to pause
+at the bottom of the screen, until a space is typed.
+
+\S{supdup-scroll} \q{Terminal scrolling}
+
+This controls whether the terminal will perform scrolling then the
+cursor goes below the last line, or if the cursor will return to the
+first line.
+
 \H{config-file} \ii{Storing configuration in a file}
 
 PuTTY does not currently support storing its configuration in a file

+ 1 - 1
source/putty/doc/copy.but

@@ -1,5 +1,5 @@
 \# Generated by licence.pl from LICENCE.
 \# You should edit those files rather than editing this one.
 
-\define{shortcopyrightdetails} 1997-2020 Simon Tatham
+\define{shortcopyrightdetails} 1997-2021 Simon Tatham
 

+ 2 - 2
source/putty/doc/errors.but

@@ -332,8 +332,8 @@ your server was rejected by the server. Usually this happens because
 the server does not provide the service which PuTTY is trying to
 access.
 
-Check that you are connecting with the correct protocol (SSH, Telnet
-or Rlogin), and check that the port number is correct. If that
+Check that you are connecting with the correct protocol (SSH, Telnet,
+etc), and check that the port number is correct. If that
 fails, consult the administrator of your server.
 
 \H{errors-conntimedout} \q{Network error: Connection timed out}

+ 12 - 2
source/putty/doc/faq.but

@@ -7,8 +7,8 @@ appendix in the manual.
 
 \S{faq-what}{Question} What is PuTTY?
 
-PuTTY is a client program for the SSH, Telnet and Rlogin network
-protocols.
+PuTTY is a client program for the SSH, Telnet, Rlogin, and SUPDUP
+network protocols.
 
 These protocols are all used to run a remote session on a computer,
 over a network. PuTTY implements the client end of that session: the
@@ -1154,6 +1154,16 @@ inactive. And Pageant spends most of its time inactive.
 
 \H{faq-admin} Administrative questions
 
+\S{faq-putty-org}{Question} Is \cw{putty.org} your website?
+
+No, it isn't. \cw{putty.org} is run by an opportunist who uses it to
+advertise their own commercial SSH implementation to people looking
+for our free one. We don't own that site, we can't control it, and we
+don't advise anyone to use it in preference to our own site.
+
+The real PuTTY web site, run by the PuTTY team, has always been at
+\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/}.
+
 \S{faq-domain}{Question} Would you like me to register you a nicer
 domain name?
 

+ 1 - 1
source/putty/doc/feedback.but

@@ -158,7 +158,7 @@ for Arm, tell us, or we'll assume you're running on Windows for
 Intel as this is overwhelmingly the case.)
 
 \b Tell us what protocol you are connecting with: SSH, Telnet,
-Rlogin, or Raw mode, or a serial connection.
+Rlogin, SUPDUP, or Raw mode, or a serial connection.
 
 \b Tell us what kind of server you are connecting to; what OS, and
 if possible what SSH server (if you're using SSH). You can get some

+ 41 - 26
source/putty/doc/gs.but

@@ -18,15 +18,15 @@ you want to connect to. You should have been told this by the
 provider of your login account.
 
 Now select a login \i{protocol} to use, from the \q{Connection type}
-buttons. For a login session, you should select \i{Telnet},
-\i{Rlogin} or \i{SSH}. See \k{which-one} for a description of the
-differences between the three protocols, and advice on which one to
-use. The fourth protocol, \I{raw protocol}\e{Raw}, is not used for
-interactive login sessions; you would usually use this for debugging
-other Internet services (see \k{using-rawprot}). The fifth option,
-\e{Serial}, is used for connecting to a local serial line, and works
-somewhat differently: see \k{using-serial} for more information on
-this.
+controls. For a login session, you should select \i{SSH}, \i{Telnet},
+\i{Rlogin}, or \i{SUPDUP}. See \k{which-one} for a description of the
+differences between these protocols, and advice on which one to
+use. The \I{raw protocol}\e{Raw} protocol is not used for interactive
+login sessions; you would usually use this for debugging other Internet
+services (see \k{using-rawprot}). The \e{Serial} option is used for
+connecting to a local serial line, and works somewhat differently:
+see \k{using-serial} for more information on this.
+\#{FIXME: describe bare ssh-connection}
 
 When you change the selected protocol, the number in the \q{Port}
 box will change. This is normal: it happens because the various
@@ -37,7 +37,7 @@ provides login services on a non-standard port, your system
 administrator should have told you which one. (For example, many
 \i{MUDs} run Telnet service on a port other than 23.)
 
-Once you have filled in the \q{Host Name}, \q{Protocol}, and
+Once you have filled in the \q{Host Name}, \q{Connection type}, and
 possibly \q{Port} settings, you are ready to connect. Press the
 \q{Open} button at the bottom of the dialog box, and PuTTY will
 begin trying to connect you to the server.
@@ -50,17 +50,15 @@ section.
 If you are using SSH to connect to a server for the first time, you
 will probably see a message looking something like this:
 
-\c The server's host key is not cached in the registry. You
-\c have no guarantee that the server is the computer you
-\c think it is.
-\c The server's rsa2 key fingerprint is:
-\c ssh-rsa 1024 7b:e5:6f:a7:f4:f9:81:62:5c:e3:1f:bf:8b:57:6c:5a
-\c If you trust this host, hit Yes to add the key to
-\c PuTTY's cache and carry on connecting.
-\c If you want to carry on connecting just once, without
-\c adding the key to the cache, hit No.
-\c If you do not trust this host, hit Cancel to abandon the
-\c connection.
+\c The server's host key is not cached in the registry. You have no
+\c guarantee that the server is the computer you think it is.
+\c The server's ssh-ed25519 key fingerprint is:
+\c  ssh-ed25519 255 SHA256:TddlQk20DVs4LRcAsIfDN9pInKpY06D+h4kSHwWAj4w
+\c If you trust this host, press "Accept" to add the key to PuTTY's
+\c cache and carry on connecting.
+\c If you want to carry on connecting just once, without adding the key
+\c to the cache, press "Connect Once".
+\c If you do not trust this host, press "Cancel" to abandon the connection.
 
 This is a feature of the SSH protocol. It is designed to protect you
 against a network attack known as \i\e{spoofing}: secretly
@@ -83,7 +81,8 @@ server, it checks that the host key presented by the server is the
 same host key as it was the last time you connected. If it is not,
 you will see a warning, and you will have the chance to abandon your
 connection before you type any private information (such as a
-password) into it.
+password) into it. (See \k{errors-hostkey-wrong} for what that looks
+like.)
 
 However, when you connect to a server you have not connected to
 before, PuTTY has no way of telling whether the host key is the
@@ -97,10 +96,26 @@ network users are on the same side and spoofing attacks are
 unlikely, so you might choose to trust the key without checking it.
 If you are connecting across a hostile network (such as the
 Internet), you should check with your system administrator, perhaps
-by telephone or in person. (Many servers have more than one
-host key. If the system administrator sends you more than one
-\I{host key fingerprint}fingerprint, you should make sure the one
-PuTTY shows you is on the list, but it doesn't matter which one it is.)
+by telephone or in person. (When verifying the fingerprint, be careful
+with letters and numbers that can be confused with each other:
+\c{0}/\c{O}, \c{1}/\c{I}/\c{l}, and so on.)
+
+Many servers have more than one host key. If the system administrator
+sends you more than one \I{host key fingerprint}fingerprint, you should
+make sure the one PuTTY shows you is on the list, but it doesn't matter
+which one it is.
+
+If you don't have any fingerprints that look like the example
+(\I{SHA256 fingerprint}\c{SHA256:} followed by a long string of
+characters), but instead have pairs of characters separated by colons
+like \c{a4:db:96:a7:...}, try pressing the \q{More info...} button and
+see if you have a fingerprint matching the \q{\i{MD5 fingerprint}}
+there. This is an older and less secure way to summarise the same
+underlying host key; it's possible for an attacker to create their
+own host key with the same fingerprint; so you should avoid relying on
+this fingerprint format unless you have no choice. The
+\q{More info...} dialog box also shows the full host public key, in
+case that is easier to compare than a fingerprint.
 
 See \k{config-ssh-hostkey} for advanced options for managing host keys.
 

+ 44 - 11
source/putty/doc/index.but

@@ -20,6 +20,12 @@
 \IM{host key fingerprint} host key fingerprint (SSH)
 \IM{host key fingerprint} SSH host key fingerprint
 
+\IM{MD5 fingerprint} MD5 fingerprint, of SSH host key
+\IM{MD5 fingerprint} fingerprint, MD5, of SSH host key
+
+\IM{SHA256 fingerprint} SHA-256 fingerprint, of SSH host key
+\IM{SHA256 fingerprint} fingerprint, SHA-256, of SSH host key
+
 \IM{manually configuring host keys} manually configuring host keys
 \IM{manually configuring host keys} overriding host keys
 \IM{manually configuring host keys} host keys, manually configuring
@@ -40,18 +46,20 @@
 \IM{different usernames}{changes of username} login names, different
 \IM{different usernames}{changes of username} account names, different
 
-\IM{differences between SSH, Telnet and Rlogin} differences between
-SSH, Telnet and Rlogin
-\IM{differences between SSH, Telnet and Rlogin} protocols,
+\IM{differences between SSH, Telnet, Rlogin, and SUPDUP} differences between
+SSH, Telnet, Rlogin, and SUPDUP
+\IM{differences between SSH, Telnet, Rlogin, and SUPDUP} protocols,
 differences between
-\IM{differences between SSH, Telnet and Rlogin} SSH, differences
-from Telnet and Rlogin
-\IM{differences between SSH, Telnet and Rlogin} Telnet, differences
-from SSH and Rlogin
-\IM{differences between SSH, Telnet and Rlogin} Rlogin, differences
-from SSH and Telnet
-\IM{differences between SSH, Telnet and Rlogin} selecting a protocol
-\IM{differences between SSH, Telnet and Rlogin} choosing a protocol
+\IM{differences between SSH, Telnet, Rlogin, and SUPDUP} SSH, differences
+from other protocols
+\IM{differences between SSH, Telnet, Rlogin, and SUPDUP} Telnet, differences
+from other protocols
+\IM{differences between SSH, Telnet, Rlogin, and SUPDUP} Rlogin, differences
+from other protocols
+\IM{differences between SSH, Telnet, Rlogin, and SUPDUP} SUPDUP, differences
+from other protocols
+\IM{differences between SSH, Telnet, Rlogin, and SUPDUP} selecting a protocol
+\IM{differences between SSH, Telnet, Rlogin, and SUPDUP} choosing a protocol
 
 \IM{MUD}{MUDs} MUDs
 
@@ -187,6 +195,11 @@ saved sessions from
 \IM{protocol selection} selecting a protocol
 \IM{protocol selection} choosing a protocol
 
+\IM{ssh-connection} bare \cw{ssh-connection} protocol
+\IM{ssh-connection} \cw{ssh-connection} protocol, bare
+
+\IM{psusan} \cq{psusan} program
+
 \IM{login name}{username} login name
 \IM{login name}{username} user name
 \IM{login name}{username} account name
@@ -218,7 +231,9 @@ saved sessions from
 \IM{-telnet} \c{-telnet} command-line option
 \IM{-raw} \c{-raw} command-line option
 \IM{-rlogin} \c{-rlogin} command-line option
+\IM{-supdup} \c{-supdup} command-line option
 \IM{-ssh} \c{-ssh} command-line option
+\IM{-ssh-connection} \c{-ssh-connection} command-line option
 \IM{-serial} \c{-serial} command-line option
 \IM{-cleanup} \c{-cleanup} command-line option
 \IM{-load} \c{-load} command-line option
@@ -262,6 +277,11 @@ saved sessions from
 \IM{PPK} \cw{PPK} file
 \IM{PPK} private key file, PuTTY
 
+\IM{Argon2} Argon2 passphrase hashing function
+
+\IM{passphrase hashing} passphrase hashing, for private key files
+\IM{passphrase hashing} password hashing, for private key files
+
 \IM{PGP key fingerprint} PGP key fingerprint
 \IM{PGP key fingerprint} fingerprint, of PGP key
 
@@ -795,6 +815,9 @@ saved sessions from
 \IM{DSA} DSA
 \IM{DSA} Digital Signature Standard
 
+\IM{EdDSA} EdDSA
+\IM{EdDSA} Edwards-curve DSA
+
 \IM{public-key algorithm} public-key algorithm
 \IM{public-key algorithm} asymmetric key algorithm
 \IM{public-key algorithm} algorithm, public-key
@@ -805,6 +828,12 @@ saved sessions from
 \IM{generating keys} public keys, generating
 \IM{generating keys} private keys, generating
 
+\IM{probable primes} probable primes
+\IM{probable primes} primes, probable
+
+\IM{proven primes} proven primes
+\IM{proven primes} primes, proven
+
 \IM{authorized_keys file}{authorized_keys} \cw{authorized_keys} file
 
 \IM{key fingerprint} fingerprint, of SSH authentication key
@@ -897,3 +926,7 @@ saved sessions from
 \IM{PuTTY icon} PuTTY icon
 \IM{PuTTY icon} icon, PuTTY's
 \IM{PuTTY icon} logo, PuTTY's
+
+\IM{system tray} system tray, Windows
+\IM{system tray} notification area, Windows (aka system tray)
+\IM{system tray} taskbar notification area, Windows (aka system tray)

+ 28 - 26
source/putty/doc/intro.but

@@ -1,43 +1,45 @@
 \C{intro} Introduction to PuTTY
 
-PuTTY is a free SSH, Telnet and Rlogin client for Windows
+PuTTY is a free SSH, Telnet, Rlogin, and SUPDUP client for Windows
 systems.
 
-\H{you-what} What are SSH, Telnet and Rlogin?
+\H{you-what} What are SSH, Telnet, Rlogin, and SUPDUP?
 
-If you already know what SSH, Telnet and Rlogin are, you can safely
-skip on to the next section.
+If you already know what SSH, Telnet, Rlogin, and SUPDUP are, you can
+safely skip on to the next section.
 
-SSH, Telnet and Rlogin are three ways of doing the same thing:
+SSH, Telnet, Rlogin, and SUPDUP are four ways of doing the same thing:
 logging in to a multi-user computer from another computer, over a
 network.
 
-Multi-user operating systems, such as Unix and VMS, usually present
-a \i{command-line interface} to the user, much like the \q{\i{Command
-Prompt}} or \q{\i{MS-DOS Prompt}} in Windows. The system prints a
-prompt, and you type commands which the system will obey.
+Multi-user operating systems, typically of the Unix family (such as
+Linux, MacOS, and the BSD family), usually present a \i{command-line
+interface} to the user, much like the \q{\i{Command Prompt}} or
+\q{\i{MS-DOS Prompt}} in Windows. The system prints a prompt, and you
+type commands which the system will obey.
 
 Using this type of interface, there is no need for you to be sitting
 at the same machine you are typing commands to. The commands, and
 responses, can be sent over a network, so you can sit at one
 computer and give commands to another one, or even to more than one.
 
-SSH, Telnet and Rlogin are \i\e{network protocols} that allow you to
-do this. On the computer you sit at, you run a \i\e{client}, which
-makes a network connection to the other computer (the \i\e{server}).
-The network connection carries your keystrokes and commands from the
-client to the server, and carries the server's responses back to
-you.
+SSH, Telnet, Rlogin, and SUPDUP are \i\e{network protocols} that allow
+you to do this. On the computer you sit at, you run a \i\e{client},
+which makes a network connection to the other computer (the
+\i\e{server}).  The network connection carries your keystrokes and
+commands from the client to the server, and carries the server's
+responses back to you.
 
 These protocols can also be used for other types of keyboard-based
 interactive session. In particular, there are a lot of bulletin
 boards, \i{talker systems} and \i{MUDs} (Multi-User Dungeons) which support
 access using Telnet. There are even a few that support SSH.
 
-You might want to use SSH, Telnet or Rlogin if:
+You might want to use SSH, Telnet, Rlogin, or SUPDUP if:
 
-\b you have an account on a Unix or VMS system which you want to be
-able to access from somewhere else
+\b you have an account on a Unix system (or some other multi-user OS
+such as VMS or ITS) which you want to be able to access from somewhere
+else
 
 \b your Internet Service Provider provides you with a login account
 on a \i{web server}. (This might also be known as a \i\e{shell account}.
@@ -47,22 +49,22 @@ your commands for you.)
 \b you want to use a \i{bulletin board system}, talker or MUD which can
 be accessed using Telnet.
 
-You probably do \e{not} want to use SSH, Telnet or Rlogin if:
+You probably do \e{not} want to use SSH, Telnet, Rlogin, or SUPDUP if:
 
 \b you only use Windows. Windows computers have their own
 ways of networking between themselves, and unless you are doing
 something fairly unusual, you will not need to use any of these
 remote login protocols.
 
-\H{which-one} How do SSH, Telnet and Rlogin differ?
+\H{which-one} How do SSH, Telnet, Rlogin, and SUPDUP differ?
 
-This list summarises some of the \i{differences between SSH, Telnet
-and Rlogin}.
+This list summarises some of the \i{differences between SSH, Telnet,
+Rlogin, and SUPDUP}.
 
 \b SSH (which stands for \q{\i{secure shell}}) is a recently designed,
 high-security protocol. It uses strong cryptography to protect your
-connection against eavesdropping, hijacking and other attacks. Telnet
-and Rlogin are both older protocols offering minimal security.
+connection against eavesdropping, hijacking and other attacks. Telnet,
+Rlogin, and SUPDUP are all older protocols offering minimal security.
 
 \b SSH and Rlogin both allow you to \I{passwordless login}log in to the
 server without having to type a password. (Rlogin's method of doing this is
@@ -82,5 +84,5 @@ doesn't support SSH, it might be worth trying to persuade the
 administrator to install it.
 
 If your client and server are both behind the same (good) firewall,
-it is more likely to be safe to use Telnet or Rlogin, but we still
-recommend you use SSH.
+it is more likely to be safe to use Telnet, Rlogin, or SUPDUP, but we
+still recommend you use SSH.

+ 2 - 2
source/putty/doc/licence.but

@@ -3,9 +3,9 @@
 
 \A{licence} PuTTY \ii{Licence}
 
-PuTTY is \i{copyright} 1997-2020 Simon Tatham.
+PuTTY is \i{copyright} 1997-2021 Simon Tatham.
 
-Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus Kuhn, Colin Watson, Christopher Staite, Lorenz Diener, Christian Brabandt, Jeff Smith, Pavel Kryukov, Maxim Kuznetsov, Svyatoslav Kuzmich, Nico Williams, Viktor Dukhovni, and CORE SDI S.A.
+Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus Kuhn, Colin Watson, Christopher Staite, Lorenz Diener, Christian Brabandt, Jeff Smith, Pavel Kryukov, Maxim Kuznetsov, Svyatoslav Kuzmich, Nico Williams, Viktor Dukhovni, Josh Dersch, Lars Brinkhoff, and CORE SDI S.A.
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \q{Software}), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 

+ 146 - 35
source/putty/doc/man-pag.but → source/putty/doc/man-pageant.but

@@ -8,18 +8,18 @@
 
 \S{pageant-manpage-synopsis} SYNOPSIS
 
-\c pageant ( -X | -T | --permanent | --debug ) [ key-file... ]
-\e bbbbbbb   bb   bb   bbbbbbbbbbb   bbbbbbb     iiiiiiii
-\c pageant [ key-file... ] --exec command [ args... ]
-\e bbbbbbb   iiiiiiii      bbbbbb iiiiiii   iiii
-\c pageant -a key-file...
-\e bbbbbbb bb iiiiiiii
-\c pageant ( -d | --public | --public-openssh ) key-identifier...
-\e bbbbbbb   bb   bbbbbbbb   bbbbbbbbbbbbbbbb   iiiiiiiiiiiiii
-\c pageant -D
-\e bbbbbbb bb
-\c pageant -l
-\e bbbbbbb bb
+\c pageant ( -X | -T | --permanent | --debug ) [ [ --encrypted ] key-file... ]
+\e bbbbbbb   bb   bb   bbbbbbbbbbb   bbbbbbb       bbbbbbbbbbb   iiiiiiii
+\c pageant [ [ --encrypted ] key-file... ] --exec command [ args... ]
+\e bbbbbbb       bbbbbbbbb   iiiiiiii      bbbbbb iiiiiii   iiii
+\c pageant -a [ --encrypted ] key-file...
+\e bbbbbbb bb   bbbbbbbbbbb   iiiiiiii
+\c pageant ( -d | -r | --public | --public-openssh ) key-identifier...
+\e bbbbbbb   bb   bb   bbbbbbbb   bbbbbbbbbbbbbbbb   iiiiiiiiiiiiii
+\c pageant ( -D | -R )
+\e bbbbbbb   bb   bb
+\c pageant -l [ --fptype format ]
+\e bbbbbbb bb   bbbbbbbb iiiiii
 \c pageant --askpass prompt
 \e bbbbbbb bbbbbbbbb iiiiii
 
@@ -41,7 +41,8 @@ extract their public half.
 
 The agent protocol used by \c{pageant} is compatible with the PuTTY
 tools and also with other implementations such as OpenSSH's SSH client
-and \e{ssh-agent(1)}.
+and \e{ssh-agent(1)}. Some \c{pageant} features are implemented with
+protocol extensions, so will only work if \c{pageant} is on both ends.
 
 To run \c{pageant} as an agent, you must provide an option to tell it
 what its \e{lifetime} should be. Typically you would probably want
@@ -75,18 +76,32 @@ extra command-line arguments, e.g.
 \c eval $(pageant -T ~/.ssh/key.ppk)
 \e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
 
-in which case Pageant will prompt for the keys' passphrases (if any)
-and start the agent with those keys already loaded. Passphrase prompts
-will use the controlling terminal if one is available, or failing that
-the GUI if one of those is available. (The prompt method can be
-overridden with the \cw{--gui-prompt} or \cw{--tty-prompt} options.)
-If neither is available, no passphrase prompting can be done.
+in which case Pageant will immediately prompt for the keys' passphrases
+(if any) and start the agent with those keys already loaded in
+cleartext form. Passphrase prompts will use the controlling terminal if
+one is available, or failing that the GUI if one of those is available.
+(The prompt method can be overridden with the \cw{--gui-prompt} or
+\cw{--tty-prompt} options.) If neither is available, no passphrase
+prompting can be done.
+
+Alternatively, you can start an agent with keys stored in encrypted
+form:
+
+\c eval $(pageant -T --encrypted ~/.ssh/key.ppk)
+\e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+
+In this case, Pageant will not prompt for a passphrase at startup;
+instead, it will prompt the first time a client tries to use the key.
+(Pageant will need access to a GUI so that it can pop up a passphrase
+prompt when required, unless it's running in \cw{--debug} mode.)
 
 To use Pageant to talk to an existing agent, you can add new keys
 using \cw{-a}, list the current set of keys' fingerprints and comments
 with \cw{-l}, extract the full public half of any key using
-\cw{--public} or \cw{--public-openssh}, delete a key using \cw{-d}, or
-delete all keys using \cw{-D}.
+\cw{--public} or \cw{--public-openssh}, delete a specific key or
+all keys using \cw{-d} or \cw{-D} respectively, or request
+re-encryption of a specific key or all keys using \cw{-r} or \cw{-R}
+respectively.
 
 \S{pageant-manpage-lifetime} LIFETIME
 
@@ -163,7 +178,8 @@ before it manages to happen.
 
 \dd Pageant will run in the foreground, without forking. It will print
 its environment variable setup commands on standard output, and then it
-will log all agent activity to standard output as well. This is useful
+will log all agent activity to standard output as well; any passphrase
+prompts will need to be answered on standard input. This is useful
 for debugging what Pageant itself is doing, or what another process is
 doing to it.
 
@@ -175,20 +191,27 @@ already have set.
 
 \dt \cw{-a} \e{key-files}
 
-\dd Load the specified private key file(s), decrypt them if necessary
-by prompting for their passphrases (with the same choice of user
-interfaces as in agent mode), and add them to the already-running agent.
+\dd Load the specified private key file(s) and add them to the
+already-running agent. Unless \cw{--encrypted} is also specified,
+\c{pageant} will decrypt them if necessary by prompting for their
+passphrases (with the same choice of user interfaces as in agent
+mode).
 
 \lcont{
-
 The private key files must be in PuTTY's \cw{.ppk} file format.
-
 }
 
 \dt \cw{-l}
 
 \dd List the keys currently in the running agent. Each key's
-fingerprint and comment string will be shown.
+fingerprint and comment string will be shown. (Use the \cw{-E}
+option to change the fingerprint format.)
+
+\lcont{
+Keys that will require a passphrase on their next use are listed as
+\q{encrypted}. Keys that can be returned to this state with \cw{-r}
+are listed as \q{re-encryptable}.
+}
 
 \dt \cw{--public} \e{key-identifiers}
 
@@ -205,8 +228,8 @@ in \cw{.ppk} format) or just its public half.
 
 \b The key's comment string, as shown by \cw{pageant -l}.
 
-\b Enough hex digits of the key's fingerprint to be unique among keys
-currently loaded into the agent.
+\b Enough of one of the key's fingerprint formats to be unique among
+keys currently loaded into the agent.
 
 If Pageant can uniquely identify one key by interpreting the
 \e{key-identifier} in any of these ways, it will assume that key was
@@ -214,9 +237,24 @@ the one you meant. If it cannot, you will have to specify more detail.
 
 If you find that your desired \e{key-identifier} string can be validly
 interpreted as more than one of the above \e{kinds} of identification,
-you can disambiguate by prefixing it with \cq{file:}, \cq{comment:} or
-\cq{fp:} to indicate that it is a filename, comment string or
-fingerprint prefix respectively.
+you can disambiguate by prefixing it as follows:
+
+\dt \cq{file:}
+
+\dd to indicate that it is a filename
+
+\dt \cq{comment:}
+
+\dd to indicate that it is a comment string
+
+\dt \cq{fp:}
+
+\dd to indicate that it is a fingerprint; any fingerprint format will
+be matched
+
+\dt \cq{sha256:} or \cq{md5:}
+
+\dd to indicate that it is a fingerprint of a specific format
 
 }
 
@@ -237,6 +275,44 @@ using \cw{pageant -a}.
 \dd Delete all keys from the agent's memory, leaving it completely
 empty.
 
+\dt \cw{-r} \e{key-identifiers}
+
+\dd \q{Re-encrypt} each specified key in the agent's memory -
+that is, forget any cleartext version, so that the user will be
+prompted for a passphrase again next time the key is used.
+(For this to be possible, the key must previously have been added
+with the \cw{--encrypted} option.)
+
+\lcont{
+(Holding encrypted keys is a Pageant extension, so this option and
+\cw{-R} are unlikely to work with other agents.)
+}
+
+\dt \cw{-R}
+
+\dd \q{Re-encrypt} all possible keys in the agent's memory.
+(This may leave some keys in cleartext, if they were not previously
+added with the \cw{--encrypted} option.)
+
+\dt \cw{--test-sign} \e{key-identifier}
+
+\dt \cw{--test-sign-with-flags=}\e{flags} \e{key-identifier}
+
+\dd Sign arbitrary data with the given key. This mode is only likely
+to be useful when testing \c{pageant} itself.
+
+\lcont{
+
+The data to sign is taken from standard input, signed by the agent
+with the key identified by \e{key-identifier}, and the resulting
+signature emitted on standard output (as a binary blob in the format
+defined by the SSH specifications).
+
+\e{flags} is a number representing a combination of flag bits defined
+by the SSH agent protocol.
+
+}
+
 \S{pageant-manpage-askpass} SSH-ASKPASS REPLACEMENT
 
 \dt \cw{--askpass} \e{prompt}
@@ -292,13 +368,48 @@ respectively. If neither option is given, Pageant will guess based on
 whether the environment variable \cw{SHELL} has a value ending in
 \cq{csh}.
 
+\dt \cw{--symlink} \e{fixed-path}
+
+\dd When operating in agent mode, as well as creating a uniquely named
+listening socket, \c{pageant} will also create (or update) a symbolic
+link at \e{fixed-path} pointing to that socket.
+
+\lcont{
+This allows access to an agent instance by setting the
+\c{SSH_AUTH_SOCK} environment variable to \e{fixed-path}, rather than
+having to use the value invented by \c{pageant} when it starts. It's
+mainly expected to be useful for debugging.
+}
+
+\dt \cw{--encrypted}, \cw{--no-decrypt}
+
+\dd When adding keys to the agent (at startup or later), keep them
+in encrypted form until the first attempt to use them; the user will
+be prompted for a passphrase then. Once decrypted, a key that was
+added in this way can be \q{re-encrypted} with the \cw{-r} or \cw{-R}
+client options.
+
+\lcont{
+The \cw{--encrypted} option makes no difference for key files which
+do not have a passphrase.
+
+(Storing keys in encrypted form is a Pageant extension; other agent
+implementations are unlikely to support it.)
+}
+
+\dt \cw{-E} \e{fingerprint-type}, \cw{--fptype} \e{fingerprint-type}
+
+\dd Specify the fingerprint format to print. Only applicable when
+listing fingerprints with \cw{-l}. The available formats are
+\cw{sha256} (the default) and \cw{md5}.
+
 \dt \cw{--gui-prompt}, \cw{--tty-prompt}
 
 \dd Force Pageant to prompt for key passphrases with a particular
 method (GUI or terminal) rather than trying to guess the most
 appropriate method as described above. (These options are relevant
-whenever an encrypted key filename is specified to \c{pageant},
-and in \c{--askpass} mode.)
+whenever a key file is specified to \c{pageant} that needs
+immediate decryption, and in \c{--askpass} mode.)
 
 \dt \cw{--help}
 

+ 20 - 2
source/putty/doc/man-pl.but → source/putty/doc/man-plink.but

@@ -56,6 +56,13 @@ to aid in verifying new files released by the PuTTY team.
 
 \dd Force serial mode.
 
+\dt \cw{-ssh-connection}
+
+\dd Force use of the \q{bare \cw{ssh-connection}} protocol. This is
+only likely to be useful when connecting to a \e{psusan(1)} server,
+most likely with an absolute path to a Unix-domain socket in place
+of \e{host}.
+
 \dt \cw{\-proxycmd} \e{command}
 
 \dd Instead of making a TCP connection, use \e{command} as a proxy;
@@ -208,8 +215,9 @@ a new connection.
 \dt \cw{\-hostkey} \e{key}
 
 \dd Specify an acceptable host public key. This option may be specified
-multiple times; each key can be either a fingerprint (\cw{99:aa:bb:...}) or
-a base64-encoded blob in OpenSSH's one-line format.
+multiple times; each key can be either a fingerprint (\cw{SHA256:AbCdE...},
+\cw{99:aa:bb:...}, etc) or a base64-encoded blob in OpenSSH's one-line
+format.
 
 \lcont{ Specifying this option overrides automated host key
 management; \e{only} the key(s) specified on the command-line will be
@@ -267,6 +275,16 @@ an effort is made to suppress obvious passwords.)
 encrypted packet data.
 }
 
+\dt \cw{\-logoverwrite}
+
+\dd If Plink is configured to write to a log file that already exists,
+discard the existing file.
+
+\dt \cw{\-logappend}
+
+\dd If Plink is configured to write to a log file that already exists,
+append new log data to the existing file.
+
 \dt \cw{\-shareexists}
 
 \dd Instead of making a new connection, test for the presence of an

+ 26 - 2
source/putty/doc/man-pscp.but

@@ -115,6 +115,19 @@ commands such as \q{\c{w}}).
 
 \dd Force use of SSH protocol version 2.
 
+\dt \cw{-ssh-connection}
+
+\dd Force use of the \q{bare \cw{ssh-connection}} protocol. This is
+only likely to be useful when connecting to a \e{psusan(1)} server,
+most likely with an absolute path to a Unix-domain socket in place
+of \e{host}.
+
+\dt \cw{-ssh}
+
+\dd Force use of the SSH protocol. (This is usually not needed; it's
+only likely to be useful if you need to override some other
+configuration of the \q{bare \cw{ssh-connection}} protocol.)
+
 \dt \cw{-4}, \cw{-6}
 
 \dd Force use of IPv4 or IPv6 for network connections.
@@ -145,8 +158,9 @@ to override a setting in a saved session.)
 \dt \cw{\-hostkey} \e{key}
 
 \dd Specify an acceptable host public key. This option may be specified
-multiple times; each key can be either a fingerprint (\cw{99:aa:bb:...}) or
-a base64-encoded blob in OpenSSH's one-line format.
+multiple times; each key can be either a fingerprint (\cw{SHA256:AbCdE...},
+\cw{99:aa:bb:...}, etc) or a base64-encoded blob in OpenSSH's one-line
+format.
 
 \lcont{ Specifying this option overrides automated host key
 management; \e{only} the key(s) specified on the command-line will be
@@ -176,6 +190,16 @@ to suppress obvious passwords.)
 encrypted packet data.
 }
 
+\dt \cw{\-logoverwrite}
+
+\dd If PSCP is configured to write to a log file that already exists,
+discard the existing file.
+
+\dt \cw{\-logappend}
+
+\dd If PSCP is configured to write to a log file that already exists,
+append new log data to the existing file.
+
 \S{pscp-manpage-more-information} MORE INFORMATION
 
 For more information on \cw{pscp} it's probably best to go and look at

+ 26 - 2
source/putty/doc/man-psft.but → source/putty/doc/man-psftp.but

@@ -103,6 +103,19 @@ commands such as \q{\c{w}}).
 
 \dd Force use of SSH protocol version 2.
 
+\dt \cw{-ssh-connection}
+
+\dd Force use of the \q{bare \cw{ssh-connection}} protocol. This is
+only likely to be useful when connecting to a \e{psusan(1)} server,
+most likely with an absolute path to a Unix-domain socket in place
+of \e{host}.
+
+\dt \cw{-ssh}
+
+\dd Force use of the SSH protocol. (This is usually not needed; it's
+only likely to be useful if you need to override some other
+configuration of the \q{bare \cw{ssh-connection}} protocol.)
+
 \dt \cw{-4}, \cw{-6}
 
 \dd Force use of IPv4 or IPv6 for network connections.
@@ -133,8 +146,9 @@ to override a setting in a saved session.)
 \dt \cw{\-hostkey} \e{key}
 
 \dd Specify an acceptable host public key. This option may be specified
-multiple times; each key can be either a fingerprint (\cw{99:aa:bb:...}) or
-a base64-encoded blob in OpenSSH's one-line format.
+multiple times; each key can be either a fingerprint (\cw{SHA256:AbCdE...},
+\cw{99:aa:bb:...}, etc) or a base64-encoded blob in OpenSSH's one-line
+format.
 
 \lcont{ Specifying this option overrides automated host key
 management; \e{only} the key(s) specified on the command-line will be
@@ -156,6 +170,16 @@ to suppress obvious passwords.)
 encrypted packet data.
 }
 
+\dt \cw{\-logoverwrite}
+
+\dd If PSFTP is configured to write to a log file that already exists,
+discard the existing file.
+
+\dt \cw{\-logappend}
+
+\dd If PSFTP is configured to write to a log file that already exists,
+append new log data to the existing file.
+
 \S{psftp-manpage-commands} COMMANDS
 
 For a list of commands available inside \cw{psftp}, type \cw{help}

+ 92 - 0
source/putty/doc/man-psocks.but

@@ -0,0 +1,92 @@
+\cfg{man-identity}{psocks}{1}{2021-04-08}{PuTTY tool suite}{PuTTY tool suite}
+
+\H{psocks-manpage} Man page for \cw{psocks}
+
+\S{psocks-manpage-name} NAME
+
+\cw{psocks} \- simple SOCKS proxy server
+
+\S{psocks-manpage-synopsis} SYNOPSIS
+
+\c psocks [ -d ] [ -f | -p pipe-cmd ] [ -g ] [ port-number ]
+\e bbbbbb   bb     bb   bb iiiiiiii     bb     iiiiiiiiiii
+
+\S{psocks-manpage-description} DESCRIPTION
+
+\cw{psocks} is a simple SOCKS4/5 proxy server. It supports proxying
+IPv4 and IPv6 connections. It does not support requiring
+authentication of its clients.
+
+\cw{psocks} can be used together with an SSH client such as
+\cw{putty(1)} to implement a reverse dynamic SSH tunnel. It can also
+be used for network protocol debugging, as it can record all the
+traffic passing through it in various ways.
+
+By default, \cw{psocks} listens to connections from localhost only,
+on TCP port 1080. A different \e{port-number} can optionally be
+supplied, and with \cw{-g} it will listen to connections from any
+host.
+
+\cw{psocks} will emit log messages about connections it receives on
+standard error. With \cw{-d}, it will log the contents of those
+connections too.
+
+\S{psocks-manpage-options} OPTIONS
+
+The command-line options supported by \cw{psocks} are:
+
+\dt \cw{-g}
+
+\dd Accept connections from anywhere. By default, \cw{psocks} only
+accepts connections on the loopback interface.
+
+\dt \cw{--exec} \e{command}
+
+\dd \cw{psocks} will run the provided command as a subprocess. When
+the subprocess terminates, \cw{psocks} will terminate as well.
+
+\lcont{
+
+All arguments on the \cw{psocks} command line after \cw{--exec} will be
+treated as part of the command to run, even if they look like other
+valid \cw{psocks} options.
+
+}
+
+\dt \cw{-d}
+
+\dd Log all traffic to standard error, in a more or less human-readable
+form (in addition to messages about connections being opened and
+closed, which are always logged).
+
+\dt \cw{-f}
+
+\dd Record all traffic to files. For every incoming connection, two
+files are created, \cw{sockout.NNNN} and \cw{sockin.NNNN}, where
+\e{NNNN} is a decimal index starting at 0 identifying the proxied
+connection. These record, respectively, traffic from the SOCKS client,
+and from the server it connected to through the proxy.
+
+\dt \cw{-p} \e{pipe-cmd}
+
+\dd Pipe all traffic to a command. For every incoming connection,
+\e{pipe-cmd} is invoked twice:
+
+\lcont{
+\c pipe-cmd out N
+\e iiiiiiii bbb i
+\c pipe-cmd in N
+\e iiiiiiii bb i
+
+Each command will run for the direction of a proxied connection, and
+have the connection's traffic piped into it, similar to \cw{-f}.
+}
+
+\S{psocks-manpage-examples} EXAMPLES
+
+In combination with the \e{plink(1)} SSH client, to set up a reverse
+dynamic SSH tunnel, in which the remote listening port 1080 on
+remote host \cw{myhost} acts as a SOCKS server giving access to your
+local network:
+
+\c psocks 12345 --exec plink -R 1080:localhost:12345 user@myhost

+ 375 - 0
source/putty/doc/man-psusan.but

@@ -0,0 +1,375 @@
+\cfg{man-identity}{psusan}{1}{2020-12-13}{PuTTY tool suite}{PuTTY tool suite}
+
+\H{psusan-manpage} Man page for \cw{psusan}
+
+\S{psusan-manpage-name} NAME
+
+\cw{psusan} \- pseudo-SSH for untappable, separately authenticated networks
+
+\S{psusan-manpage-synopsis} SYNOPSIS
+
+\c psusan [ options ]
+\e bbbbbb   iiiiiii
+
+\S{psusan-manpage-description} DESCRIPTION
+
+\cw{psusan} is a server program that behaves like the innermost
+\q{connection} layer of an SSH session, without the two outer security
+layers of encryption and authentication. It provides all the
+post-authentication features of an SSH connection:
+
+\b choosing whether to run an interactive terminal session or a single
+specified command
+
+\b multiple terminal sessions at once (or a mixture of those and
+specified commands)
+
+\b SFTP file transfer 
+
+\b all the standard SSH port-forwarding options
+
+\b X11 forwarding
+
+\b SSH agent forwarding
+
+The catch is that, because it lacks the outer layers of SSH, you have
+to run it over some kind of data channel that is already authenticated
+as the right user, and that is already protected to your satisfaction
+against eavesdropping and session hijacking. A good rule of thumb is
+that any channel that you were prepared to run a \e{bare} shell
+session over, you can run \cw{psusan} over instead, which adds all the
+above conveniences without changing the security properties.
+
+The protocol that \cw{psusan} speaks is also spoken by PuTTY, Plink,
+PSCP, and PSFTP, if you select the protocol type \q{Bare ssh-connection}
+or the command-line option \cw{-ssh-connection} and specify the
+absolute path to the appropriate Unix-domain socket in place of
+a hostname.
+
+\S{psusan-manpage-examples} EXAMPLES
+
+The idea of a secure, pre-authenticated data channel seems strange to
+people thinking about \e{network} connections. But there are lots of
+examples within the context of a single Unix system, and that's where
+\cw{psusan} is typically useful.
+
+\S2{psusan-manpage-examples-docker} Docker
+
+A good example is the console or standard I/O channel leading into a
+container or virtualisation system. Docker is a familiar example. If
+you want to start a Docker container and run a shell directly within
+it, you might say something like
+
+\c docker run -i -t some:image
+\e                  iiiiiiiiii
+
+which will allow you to run a single shell session inside the
+container, in the same terminal you started Docker from.
+
+Suppose that you'd prefer to run \e{multiple} shell sessions in the
+same container at once (perhaps so that one of them can use debugging
+tools to poke at what another is doing). And perhaps inside that
+container you're going to run a program that you don't trust with full
+access to your network, but are prepared to let it make one or two
+specific network connections of the kind you could set up with an SSH
+port forwarding.
+
+In that case, you could remove the \cw{-t} option from that Docker
+command line (which means \q{allocate a terminal device}), and tell it
+to run \cw{psusan} inside the container:
+
+\c docker run -i some:image /some/path/to/psusan
+\e               iiiiiiiiii  iiiiiiiiiiii 
+
+(Of course, you'll need to ensure that \cw{psusan} is installed
+somewhere inside the container image.)
+
+If you do that from a shell command line, you'll see a banner line
+looking something like this:
+
+\c [email protected]_Release_0.75
+
+which isn't particularly helpful except that it tells you that
+\cw{psusan} has started up successfully.
+
+To talk to this server \e{usefully}, you can set up a PuTTY saved
+session as follows:
+
+\b Set the protocol to \q{Bare ssh-connection} (the \cw{psusan}
+protocol).
+
+\b Write \e{something} in the hostname box. It will appear in PuTTY's
+window title (if you run GUI PuTTY), so you might want to write
+something that will remind you what kind of window it is. If you have
+no opinion, something generic like \cq{dummy} will do.
+
+\b In the \q{Proxy} configuration panel, set the proxy type to
+\q{Local}, and enter the above \cq{docker run} command in the
+\q{Telnet command, or local proxy command} edit box.
+
+\b In the \q{SSH} configuration panel, you will very likely want to
+turn on connection sharing. (See below.)
+
+This arranges that when PuTTY starts up, it will run the Docker
+command as shown above in place of making a network connection, and
+talk to that command using the \cw{psusan} SSH-like protocol.
+
+The effect is that you will still get a shell session in the context
+of a Docker container. But this time, it's got all the SSH amenities.
+If you also turn on connection sharing in the \q{SSH} configuration
+panel, then the \q{Duplicate Session} option will get you a second
+shell in the \e{same} Docker container (instead of a primary shell in
+a separate instance). You can transfer files in and out of the
+container while it's running using PSCP or PSFTP; you can forward
+network ports, X11 programs, and/or an SSH agent to the container.
+
+Of course, another way to do all of this would be to run the \e{full}
+SSH protocol over the same channel. This involves more setup: you have
+to invent an SSH host key for the container, accept it in the client,
+and deal with it being left behind in your client's host key cache
+when the container is discarded. And you have to set up some login
+details in the container: either configure a password, and type it in
+the client, or copy in the public half of some SSH key you already
+had. And all this inconvenience is \e{unnecessary}, because these are
+all precautions you need to take when the connection between two
+systems is going over a hostile network. In this case, it's only going
+over a kernel IPC channel that's guaranteed to go to the right place,
+so those safety precautions are redundant, and they only add
+awkwardness.
+
+\S2{psusan-manpage-examples-uml} User-mode Linux
+
+User-mode Linux is another container type you can talk to in the same
+way. Here's a small worked example.
+
+The \e{easiest} way to run UML is to use its \cq{hostfs} file system
+type to give the guest kernel access to the same virtual filesystem as
+you have on the host. For example, a command line like this gets you a
+shell prompt inside a UML instance sharing your existing filesystem:
+
+\c linux mem=512M rootfstype=hostfs rootflags=/ rw init=/bin/bash
+
+If you run this at a command line (assuming you have a UML kernel
+available on your path under the name \cq{linux}), then you should see
+a lot of kernel startup messages, followed by a shell prompt along the
+lines of
+
+\c root@(none):/#
+
+To convert this into a \cw{psusan}-based UML session, we need to
+adjust the command line so that instead of running \cw{bash} it runs
+\cw{psusan}. But running \cw{psusan} directly isn't quite enough,
+because \cw{psusan} will depend on a small amount of setup, such as
+having \cw{/proc} mounted. So instead, we set the init process to a
+shell script which will do the necessary setup and \e{then} invoke
+\cw{psusan}.
+
+Also, running \cw{psusan} directly over the UML console device is a
+bad idea, because then the \cw{psusan} binary protocol will be mixed
+with textual console messages. So a better plan is to redirect UML's
+console to the standard error of the \cw{linux} process, and map its
+standard input and output to a serial port. So the replacement UML
+command line might look something like this:
+
+\c linux mem=512M rootfstype=hostfs rootflags=/ rw \
+\c     con=fd:2,fd:2 ssl0=fd:0,fd:1 init=/some/path/to/uml-psusan.sh
+\e                                       iiiiiiiiiiiiiiiiiiiiiiiiiii
+
+And the setup script \cw{uml-psusan.sh} might look like this:
+
+\c #!/bin/bash
+\c # Set up vital pseudo-filesystems
+\e iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
+\c mount -t proc none /proc
+\c mount -t devpts none /dev/pts
+\c # Redirect I/O to the serial port, but stderr to the console
+\e iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
+\c exec 0<>/dev/ttyS0 1>&0 2>/dev/console
+\c # Set the serial port into raw mode, to run a binary protocol
+\e iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
+\c stty raw -echo
+\c # Choose what shell you want to run inside psusan
+\e iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
+\c export SHELL=/bin/bash
+\c # And now run psusan over the serial port
+\e iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
+\c exec /home/simon/src/putty/misc/psusan
+
+Now set up a PuTTY saved session as in the Docker example above, using
+that \cw{linux} command as the local proxy command, and you'll have a
+PuTTY session that starts up a clean UML instance when you run it, and
+(if you enabled connection sharing) further instances of the same
+session will connect to the same instance again.
+
+\S2{psusan-manpage-examples-wsl} Windows Subsystem for Linux
+
+On Windows, the default way to use WSL is to run the \cw{wsl} program,
+or one of its aliases, in a Windows console, either by launching it
+from an existing command prompt, or by using a shortcut that opens it
+in a fresh console. This gives you a Linux terminal environment, but
+in a Windows console window.
+
+If you'd prefer to interact with the same environment using PuTTY as
+the terminal (for example, if you prefer PuTTY's mouse shortcuts for
+copy and paste), you can set it up by installing \cw{psusan} in the
+Linux environment, and then setting up a PuTTY saved session that
+talks to it. A nice way to do this is to use the name of the WSL
+distribution as the \q{host name}:
+
+\b set the local proxy command to \cq{wsl -d %host
+/usr/local/bin/psusan} (or wherever you installed \cw{psusan} in the
+Linux system)
+
+\b enter the name of a particular WSL distribution in the host name
+box. (For example, if you installed WSL Debian in the standard way
+from the Windows store, this will just be \q{Debian}.)
+
+\b set the protocol to \q{Bare ssh-connection}, as usual.
+
+Like all the other examples here, this also permits you to forward
+ports in and out of the WSL environment (e.g. expose a WSL2 network
+service through the hypervisor's internal NAT), forward Pageant into
+it, and so on.
+
+\S2{psusan-manpage-examples-schroot} \cw{schroot}
+
+Another example of a container-like environment is the alternative
+filesystem layout set up by \cw{schroot}(\e{1}).
+
+\cw{schroot} is another program that defaults to running an
+interactive shell session in the terminal you launched it from. But
+again, you can get a \cw{psusan} connection into the \cw{schroot}
+environment by setting up a PuTTY saved session whose local proxy
+command is along the lines of
+
+\c schroot -c chroot-name /some/path/to/psusan
+\e            iiiiiiiiiii  iiiiiiiiiiii
+
+Depending on how much of the chroot environment is copied from your
+main one, you might find this makes it easier to (for example) run X11
+programs inside the chroot that open windows on your main X display,
+or transfer files in and out of the chroot.
+
+\S2{psusan-manpage-examples-namespace} Between network namespaces
+
+If you've set up multiple network namespaces on a Linux system, with
+different TCP/IP configurations, then \cw{psusan} can be a convenient
+unprivileged-user gateway between them, if you run it as a non-root
+user in the non-default one of your namespaces, listening for
+connections on a Unix-domain socket.
+
+If you do that, then it gives you convenient control over which of
+your outgoing network connections use which TCP/IP configuration: you
+can use PuTTY to run a shell session in the context of the other
+namespace if you want to run commands like \cw{ping}, or you can set
+up individual port forwardings or even a SOCKS server so that
+processes running in one namespace can send their network connections
+via the other one.
+
+For this application, it's probably most convenient to use the
+\cw{--listen} option in \cw{psusan}, which makes it run as a server
+and listen for connections on a Unix-domain socket. Then you can enter
+that socket name in PuTTY's host name configuration field (and also
+still select the \q{Bare ssh-connection} protocol option), to connect
+to that socket as if it were an SSH client.
+
+Provided the Unix-domain socket is inside a directory that only the
+right user has access to, this will ensure that authentication is done
+implicitly by the Linux kernel.
+
+\S2{psusan-manpage-examples-userv} Between user ids, via GNU userv
+
+If you use multiple user ids on the same machine, say for purposes of
+privilege separation (running some less-trusted program with limited
+abilities to access all your stuff), then you probably have a
+\q{default} or most privileged account where you run your main login
+session, and sometimes need to run a shell in another account.
+
+\cw{psusan} can be used as an access channel between the accounts,
+using GNU \cw{userv}(\e{1}) as the transport. In the account you want
+to access, write a \cw{userv} configuration stanza along the lines of
+
+\c if (glob service psusan & glob calling-user my-main-account-name)
+\e                                             iiiiiiiiiiiiiiiiiiii
+\c    reset
+\c    execute /some/path/to/psusan
+\e             iiiiiiiiiiii
+\c fi
+
+This gives your main account the right to run the command
+
+\c userv my-sub-account-name psusan
+\e       iiiiiiiiiiiiiiiiiii
+
+and you can configure that command name as a PuTTY local proxy
+command, in the same way as most of the previous examples.
+
+Of course, there are plenty of ways already to access one local
+account from another, such as \cw{sudo}. One advantage of doing it
+this way is that you don't need the system administrator to intervene
+when you want to change the access controls (e.g. change which of your
+accounts have access to another): as long as you have \e{some} means
+of getting into each account in the first place, and \cw{userv} is
+installed, you can make further configuration changes without having
+to bother root about it.
+
+Another advantage is that it might make file transfer between the
+accounts easier. If you're the kind of person who keeps your home
+directories private, then it's awkward to copy a file from one of your
+accounts to another just by using the \cw{cp} command, because there's
+nowhere convenient that you can leave it in one account where the
+other one can read it. But with \cw{psusan} over \cw{userv}, you don't
+need any shared piece of filesystem: you can \cw{scp} files back and
+forth without any difficulty.
+
+\S{psusan-manpage-options} OPTIONS
+
+The command-line options supported by \cw{psusan} are:
+
+\dt \cw{--listen} \e{unix-socket-name}
+
+\dd Run \cw{psusan} in listening mode. \e{unix-socket-name} is the
+pathname of a Unix-domain socket to listen on. You should ensure that
+this pathname is inside a directory whose read and exec permissions
+are restricted to only the user(s) you want to be able to access the
+environment that \cw{psusan} is running in.
+
+\lcont{
+
+The listening socket has to be a Unix-domain socket. \cw{psusan} does
+not provide an option to run over TCP/IP, because the unauthenticated
+nature of the protocol would make it inherently insecure.
+
+}
+
+\dt \cw{--listen-once}
+
+\dd In listening mode, this option causes \cw{psusan} to listen for
+only one connection, and exit immediately after that connection
+terminates.
+
+\dt \cw{--sessiondir} \e{pathname}
+
+\dd This option sets the directory that shell sessions and
+subprocesses will start in. By default it is \cw{psusan}'s own working
+directory, but in some situations it's easier to change it with a
+command-line option than by wrapping \cw{psusan} in a script that
+changes directory before starting it.
+
+\dt \cw{-v}, \cw{--verbose}
+
+\dd This option causes \cw{psusan} to print verbose log messages on
+its standard error. This is probably most useful in listening mode.
+
+\dt \cw{\-sshlog} \e{logfile}
+
+\dt \cw{\-sshrawlog} \e{logfile}
+
+\dd These options cause \cw{psusan} to log protocol details to a file,
+similarly to the logging options in PuTTY and Plink.
+
+\lcont{
+\cw{\-sshlog} logs decoded SSH packets and other events (those that
+\cw{\-v} would print). \cw{\-sshrawlog} additionally logs the raw wire
+data, including the outer packet format and the initial greetings.
+}

+ 0 - 0
source/putty/doc/man-pter.but → source/putty/doc/man-pterm.but


+ 18 - 6
source/putty/doc/man-putt.but → source/putty/doc/man-putty.but

@@ -4,7 +4,7 @@
 
 \S{putty-manpage-name} NAME
 
-\cw{putty} - GUI SSH, Telnet and Rlogin client for X
+\cw{putty} - GUI SSH, Telnet, Rlogin, and SUPDUP client for X
 
 \S{putty-manpage-synopsis} SYNOPSIS
 
@@ -13,8 +13,8 @@
 
 \S{putty-manpage-description} DESCRIPTION
 
-\cw{putty} is a graphical SSH, Telnet and Rlogin client for X. It is
-a direct port of the Windows SSH client of the same name.
+\cw{putty} is a graphical SSH, Telnet, Rlogin, and SUPDUP client for
+X. It is a direct port of the Windows SSH client of the same name.
 
 \S{putty-manpage-options} OPTIONS
 
@@ -128,6 +128,16 @@ an effort is made to suppress obvious passwords.)
 encrypted packet data.
 }
 
+\dt \cw{\-logoverwrite}
+
+\dd If \cw{putty} is configured to write to a log file that already exists,
+discard the existing file.
+
+\dt \cw{\-logappend}
+
+\dd If \cw{putty} is configured to write to a log file that already exists,
+append new log data to the existing file.
+
 \dt \cw{\-cs} \e{charset}
 
 \dd This option specifies the character set in which \cw{putty}
@@ -174,7 +184,8 @@ in verifying new files released by the PuTTY team.
 straight from the command line without having to go through the
 configuration box first.
 
-\dt \cw{\-ssh}, \cw{\-telnet}, \cw{\-rlogin}, \cw{\-raw}, \cw{\-serial}
+\dt \cw{\-ssh}, \cw{\-telnet}, \cw{\-rlogin}, \cw{\-supdup}, \cw{\-raw},
+\cw{-ssh-connection}, \cw{\-serial}
 
 \dd Select the protocol \cw{putty} will use to make the connection.
 
@@ -279,8 +290,9 @@ to override a setting in a saved session.)
 \dt \cw{\-hostkey} \e{key}
 
 \dd Specify an acceptable host public key. This option may be specified
-multiple times; each key can be either a fingerprint (\cw{99:aa:bb:...}) or
-a base64-encoded blob in OpenSSH's one-line format.
+multiple times; each key can be either a fingerprint (\cw{SHA256:AbCdE...},
+\cw{99:aa:bb:...}, etc) or a base64-encoded blob in OpenSSH's one-line
+format.
 
 \lcont{ Specifying this option overrides automated host key
 management; \e{only} the key(s) specified on the command-line will be

+ 144 - 15
source/putty/doc/man-pg.but → source/putty/doc/man-puttygen.but

@@ -8,12 +8,14 @@
 
 \S{puttygen-manpage-synopsis} SYNOPSIS
 
-\c puttygen ( keyfile | -t keytype [ -b bits ] )
-\e bbbbbbbb   iiiiiii   bb iiiiiii   bb iiii
-\c          [ -C new-comment ] [ -P ] [ -q ]
-\e            bb iiiiiiiiiii     bb     bb
-\c          [ -O output-type | -l | -L | -p ]
-\e            bb iiiiiiiiiii   bb   bb   bb
+\c puttygen ( keyfile | -t keytype [ -b bits ] [ --primes method ] [ -q ] )
+\e bbbbbbbb   iiiiiii   bb iiiiiii   bb iiii     bbbbbbbb iiiiii     bb
+\c          [ -C new-comment ] [ -P ] [ --reencrypt ]
+\e            bb iiiiiiiiiii     bb     bbbbbbbbbbb
+\c          [ -O output-type | -l | -L | -p | --dump ] [ -E fptype ]
+\e            bb iiiiiiiiiii   bb   bb   bb   bbbbbb     bb iiiiii
+\c             [ --ppk-param key=value,... ]
+\e               bbbbbbbbbbb iiibiiiiib
 \c          [ -o output-file ]
 \e            bb iiiiiiiiiii
 
@@ -26,7 +28,7 @@ also interoperate with the key formats used by some other SSH clients.
 When you run \c{puttygen}, it does three things. Firstly, it either
 loads an existing key file (if you specified \e{keyfile}), or
 generates a new key (if you specified \e{keytype}). Then, it
-optionally makes modifications to the key (changing the comment
+optionally makes modifications to the key (such as changing the comment
 and/or the passphrase); finally, it outputs the key, or some
 information about the key, to a file.
 
@@ -44,7 +46,8 @@ The options to control this phase are:
 
 \dt \e{keyfile}
 
-\dd Specify a key file to be loaded.
+\dd Specify a key file to be loaded. (Use \cq{-} to read a key
+file from standard input.)
 
 \lcont{
 
@@ -63,12 +66,50 @@ OpenSSH format, or the standard SSH-1 format.
 \dt \cw{\-t} \e{keytype}
 
 \dd Specify a type of key to generate. The acceptable values here are
-\c{rsa}, \c{dsa}, \c{ecdsa}, and \c{ed25519} (to generate SSH-2 keys),
-and \c{rsa1} (to generate SSH-1 keys).
+\c{rsa}, \c{dsa}, \c{ecdsa}, \c{eddsa}, \c{ed25519}, and \c{ed448}
+(to generate SSH-2 keys), and \c{rsa1} (to generate SSH-1 keys).
 
 \dt \cw{\-b} \e{bits}
 
-\dd Specify the size of the key to generate, in bits. Default is 2048.
+\dd Specify the size of the key to generate, in bits. Default for
+\c{rsa} and \c{dsa} keys is 2048.
+
+\dt \cw{\-\-primes} \e{method}
+
+\dd Method for generating prime numbers. The acceptable values here
+are \c{probable} (the default), \c{proven}, and \c{proven-even};
+the later methods are slower. (Various synonyms for these method
+names are also accepted.)
+
+\lcont{
+
+The \q{probable primes} method sounds unsafe, but it's the most
+commonly used prime-generation strategy. There is in theory a
+possibility that it might accidentally generate a number that isn't
+prime, but the software does enough checking to make that probability
+vanishingly small (less than 1 in 2^80, or 1 in 10^24). So, in
+practice, nobody worries about it very much.
+
+The other methods cause PuTTYgen to use numbers that it is \e{sure}
+are prime, because it generates the output number together with a
+proof of its primality. This takes more effort, but it eliminates that
+theoretical risk in the probabilistic method.
+
+You might choose to switch from probable to proven primes if you have
+a local security standard that demands it, or if you don't trust the
+probabilistic argument for the safety of the usual method.
+
+}
+
+\dt \cw{\-\-strong-rsa}
+
+\dd When generating an RSA key, make sure the prime factors of the key
+modulus are \q{strong primes}. A strong prime is a prime number chosen
+to have a particular structure that makes certain factoring algorithms
+more difficult to apply, so some security standards recommend their
+use. However, the most modern factoring algorithms are unaffected, so
+this option is probably not worth turning on \e{unless} you have a
+local standard that recommends it.
 
 \dt \cw{\-q}
 
@@ -102,6 +143,70 @@ to type).
 automatic when you are generating a new key, but not when you are
 modifying an existing key.
 
+\dt \cw{\-\-reencrypt}
+
+\dd For an existing private key saved with a passphrase, refresh the
+encryption without changing the passphrase.
+
+\lcont{
+This is most likely to be useful with the \cw{\-\-ppk-param} option,
+to change some aspect of the key file's format or encryption.
+}
+
+\dt \cw{\-\-ppk-param} \e{key}\cw{=}\e{value}\cw{,}...
+
+\dd When saving a PPK file (the default \cw{private} output type for SSH-2
+keys), adjust details of the on-disk format.
+
+\lcont{
+
+Aspects to change are specified as a series of \e{key}\cw{=}\e{value} pairs
+separated by commas. The \e{key}s are:
+
+\dt \cw{version}
+
+\dd The PPK format version. Possible values are \cw{3} (the default)
+and \cw{2} (which is less resistant to brute-force decryption, but
+which you might need if your key needs to be used by old versions of
+PuTTY tools, or other PPK consumers).
+
+\lcont{
+The following \e{key}s only affect PPK version 3 files.
+}
+
+\dt \cw{kdf}
+
+\dd The variant of the Argon2 key derivation function to use. Options
+are \cw{argon2id} (default, and recommended), \cw{argon2i}, and
+\cw{argon2d}.
+
+\lcont{
+You might change this if you consider your exposure to side-channel
+attacks to be different to the norm.
+}
+
+\dt \cw{memory}
+
+\dd The amount of memory needed to decrypt the key, in Kbyte. Default
+is 8192 (i.e., 8 Mbyte).
+
+\dt \cw{time}
+
+\dd Approximate time, on this machine, required to attempt decrypting
+the key, in milliseconds. Default is 100 (ms).
+
+\dt \cw{passes}
+
+\dd Alternative to \cw{time}: explicitly specify the number of hash
+passes required to attempt decrypting the key.
+
+\dt \cw{parallelism}
+
+\dd Number of parallelisable threads that can be used to decrypt the
+key. Default is 1 (force decryption to run single-threaded).
+
+}
+
 In the third phase, \c{puttygen} saves the key or information
 about it. The options to control this are:
 
@@ -115,7 +220,8 @@ Acceptable options are:
 \dt \cw{private}
 
 \dd Save the private key in a format usable by PuTTY. This will either
-be the standard SSH-1 key format, or PuTTY's own SSH-2 key format.
+be the standard SSH-1 key format, or PuTTY's own SSH-2 key format
+(\q{PPK}). This is the default.
 
 \dt \cw{public}
 
@@ -134,8 +240,9 @@ which is a single line (\q{\cw{ssh-rsa AAAAB3NzaC1yc2}...}).
 
 \dt \cw{fingerprint}
 
-\dd Print the fingerprint of the public key. All fingerprinting
-algorithms are believed compatible with OpenSSH.
+\dd Print a fingerprint of the public key. The \cw{-E} option lets you
+specify which fingerprinting algorithm to use. All algorithms are
+believed compatible with OpenSSH.
 
 \dt \cw{private-openssh}
 
@@ -153,6 +260,19 @@ newer format even for RSA, DSA, and ECDSA keys.
 \dd Save an SSH-2 private key in ssh.com's format. This option is not
 permitted for SSH-1 keys.
 
+\dt \cw{text}
+
+\dd Save a textual dump of the numeric components comprising the key
+(both the public and private parts, if present). Useful for debugging,
+or for using PuTTYgen as a key generator for applications other than
+SSH.
+
+\lcont{
+The output consists of a series of \cw{name=value} lines, where each
+\c{value} is either a C-like string literal in double quotes, or a
+hexadecimal number starting with \cw{0x...}
+}
+
 If no output type is specified, the default is \c{private}.
 
 }
@@ -178,6 +298,15 @@ fingerprint. Otherwise, the \c{\-o} option is required.
 
 \dd Synonym for \q{\cw{-O public}}.
 
+\dt \cw{\-\-dump}
+
+\dd Synonym for \q{\cw{-O text}}.
+
+\dt \cw{-E} \e{fptype}
+
+\dd Specify the algorithm to use if generating a fingerprint. The
+options are \cw{sha256} (the default) and \cw{md5}.
+
 \dt \cw{\-\-new\-passphrase} \e{file}
 
 \dd Specify a file name; the first line will be read from this file
@@ -231,7 +360,7 @@ automatically detect the input key type):
 
 \c puttygen my-ssh.com-key -o mykey.ppk
 
-To display the fingerprint of a key (some key types require a
+To display the SHA-256 fingerprint of a key (some key types require a
 passphrase to extract even this much information):
 
 \c puttygen -l mykey.ppk

+ 5 - 5
source/putty/doc/man-ptel.but → source/putty/doc/man-puttytel.but

@@ -4,7 +4,7 @@
 
 \S{puttytel-manpage-name} NAME
 
-\cw{puttytel} \- GUI Telnet and Rlogin client for X
+\cw{puttytel} \- GUI Telnet, Rlogin, and SUPDUP client for X
 
 \S{puttytel-manpage-synopsis} SYNOPSIS
 
@@ -13,9 +13,9 @@
 
 \S{puttytel-manpage-description} DESCRIPTION
 
-\cw{puttytel} is a graphical Telnet and Rlogin client for X. It
-is a direct port of the Windows Telnet and Rlogin client of the same
-name, and a cut-down cryptography-free version of PuTTY.
+\cw{puttytel} is a graphical Telnet, Rlogin, and SUPDUP client for X. It
+is a direct port of the Windows Telnet, Rlogin, and SUPDUP client of the
+same name, and a cut-down cryptography-free version of PuTTY.
 
 \S{puttytel-manpage-options} OPTIONS
 
@@ -161,7 +161,7 @@ in verifying new files released by the PuTTY team.
 straight from the command line without having to go through the
 configuration box first.
 
-\dt \cw{\-telnet}, \cw{\-rlogin}, \cw{\-raw}
+\dt \cw{\-telnet}, \cw{\-rlogin}, \cw{\-supdup}, \cw{\-raw}
 
 \dd Select the protocol \cw{puttytel} will use to make the connection.
 

+ 72 - 4
source/putty/doc/pageant.but

@@ -11,7 +11,8 @@ format. See \k{pubkey} to find out how to generate and use one.
 
 When you run Pageant, it will put an icon of a computer wearing a
 hat into the \ii{System tray}. It will then sit and do nothing, until you
-load a private key into it.
+load a private key into it. (You may need to use Windows'
+\q{Show hidden icons} arrow to see the Pageant icon.)
 
 If you click the Pageant icon with the right mouse button, you will
 see a menu. Select \q{View Keys} from this menu. The Pageant main
@@ -46,6 +47,9 @@ When you want to shut down Pageant, click the right button on the
 Pageant icon in the System tray, and select \q{Exit} from the menu.
 Closing the Pageant main window does \e{not} shut down Pageant.
 
+If you want Pageant to stay running but forget all the keys it has
+acquired, select \q{Remove All Keys} from the System tray menu.
+
 \H{pageant-mainwin} The Pageant main window
 
 The Pageant main window appears when you left-click on the Pageant
@@ -60,8 +64,8 @@ The large list box in the Pageant main window lists the private keys
 that are currently loaded into Pageant. The list might look
 something like this:
 
-\c ssh-rsa 2048 22:d6:69:c9:22:51:ac:cb:b9:15:67:47:f7:65:6d:d7 k1
-\c ssh-dss 2048 e4:6c:69:f3:4f:fc:cf:fc:96:c0:88:34:a7:1e:59:d7 k2
+\c ssh-ed25519  SHA256:TddlQk20DVs4LRcAsIfDN9pInKpY06D+h4kSHwWAj4w
+\c ssh-rsa 2048 SHA256:8DFtyHm3kQihgy52nzX96qMcEVOq7/yJmmwQQhBWYFg
 
 For each key, the list box will tell you:
 
@@ -70,17 +74,29 @@ For each key, the list box will tell you:
 \c{ssh-dss} (a DSA key for use with the SSH-2 protocol),
 \c{ecdsa-sha2-*} (an ECDSA key for use with the SSH-2 protocol),
 \c{ssh-ed25519} (an Ed25519 key for use with the SSH-2 protocol),
+\c{ssh-ed448} (an Ed448 key for use with the SSH-2 protocol),
 or \c{ssh1} (an RSA key for use with the old SSH-1 protocol).
 
-\b The size (in bits) of the key.
+\b The size (in bits) of the key, for key types that come in different
+sizes.
 
 \b The \I{key fingerprint}fingerprint for the public key. This should be
 the same fingerprint given by PuTTYgen, and (hopefully) also the same
 fingerprint shown by remote utilities such as \i\c{ssh-keygen} when
 applied to your \c{authorized_keys} file.
 
+\lcont{
+By default this is shown in the \q{SHA256} format. You can change to the
+older \q{MD5} format (which looks like \c{aa:bb:cc:...}) with the
+\q{Fingerprint type} drop-down, but bear in mind that this format is
+less secure and should be avoided for comparison purposes where possible.
+}
+
 \b The comment attached to the key.
 
+\b The state of deferred decryption, if enabled for this key.
+See \k{pageant-deferred-decryption}.
+
 \S{pageant-mainwin-addkey} The \q{Add Key} button
 
 To add a key to Pageant by reading it out of a local disk file,
@@ -138,6 +154,9 @@ passphrases on startup.
 If Pageant is already running, this syntax loads keys into the
 existing Pageant.
 
+You can specify the \cq{--encrypted} option to defer decryption of
+these keys; see \k{pageant-deferred-decryption}.
+
 \S{pageant-cmdline-command} Making Pageant run another program
 
 You can arrange for Pageant to start another program once it has
@@ -151,6 +170,11 @@ by the command, like this:
 
 \c C:\PuTTY\pageant.exe d:\main.ppk -c C:\PuTTY\putty.exe
 
+\S{pageant-cmdline-keylist} Starting with the key list visible
+
+Start Pageant with the \i\c{--keylist} option to show the main window
+as soon as it starts up.
+
 \S{pageant-cmdline-restrict-acl} Restricting the \i{Windows process ACL}
 
 Pageant supports the same \i\c{-restrict-acl} option as the other
@@ -221,6 +245,50 @@ you can send it all the way back to Pageant using the local
 and then it's available to every machine that has agent forwarding
 available (not just the ones downstream of the place you added it).
 
+\H{pageant-deferred-decryption} Loading keys without decrypting them
+
+You can add keys to Pageant \e{without} decrypting them. The key
+file will be held in Pageant's memory still encrypted, and when a
+client program first tries to use the key, Pageant will display a
+dialog box prompting for the passphrase so that the key can be
+decrypted.
+
+This works the same way whether the key is used by an instance of
+PuTTY running locally, or a remote client connecting to Pageant
+through agent forwarding.
+
+To add a key to Pageant in this encrypted form, press the \q{Add Key
+(encrypted)} button in the Pageant main window, or alternatively
+right-click on the Pageant icon in the system tray and select \q{Add
+Key (encrypted)} from there. Pageant will bring up a file dialog, in
+just the same way as it would for the plain \q{Add Key} button. But it
+won't ask for a passphrase. Instead, the key will be listed in the
+main window with \q{(encrypted)} after it.
+
+To start Pageant up in the first place with encrypted keys loaded into
+it, you can use the \cq{--encrypted} option on the command line. For
+example:
+
+\c C:\PuTTY\pageant.exe --encrypted d:\main.ppk
+
+After a key has been decrypted for the first use, it remains
+decrypted, so that it can be used again. The main window will list
+the key with \q{(\i{re-encryptable})} after it. You can revert it
+to the previous state, where a passphrase is required, using the
+\q{\i{Re-encrypt}} button in the Pageant main window.
+
+You can also \q{re-encrypt} all keys that were added encrypted by
+choosing \q{Re-encrypt All Keys} from the System tray menu.
+(Note that this does \e{not} discard cleartext keys that were not
+previously added encrypted!)
+
+\s{CAUTION}: When Pageant displays a prompt to decrypt an
+already-loaded key, it cannot give keyboard focus to the prompt dialog
+box. As far as I know this is a deliberate defensive measure by
+Windows, against malicious software. So make sure you click in the
+prompt window before typing your passphrase, or else the passphrase
+might be sent to somewhere you didn't want to trust with it!
+
 \H{pageant-security} Security considerations
 
 \I{security risk}Using Pageant for public-key authentication gives you the

+ 25 - 18
source/putty/doc/plink.but

@@ -41,7 +41,7 @@ use Plink:
 
 \c C:\>plink
 \c Plink: command-line connection utility
-\c Release 0.74
+\c Release 0.75
 \c Usage: plink [options] [user@]host [command]
 \c        ("host" can also be a PuTTY saved session name)
 \c Options:
@@ -51,6 +51,8 @@ use Plink:
 \c   -load sessname  Load settings from saved session
 \c   -ssh -telnet -rlogin -raw -serial
 \c             force use of a particular protocol
+\c   -ssh-connection
+\c             force use of the bare ssh-connection protocol
 \c   -P port   connect to specified port
 \c   -l user   connect with specified username
 \c   -batch    disable all interactive prompts
@@ -69,7 +71,7 @@ use Plink:
 \c   -X -x     enable / disable X11 forwarding
 \c   -A -a     enable / disable agent forwarding
 \c   -t -T     enable / disable pty allocation
-\c   -1 -2     force use of particular protocol version
+\c   -1 -2     force use of particular SSH protocol version
 \c   -4 -6     force use of IPv4 or IPv6
 \c   -C        enable compression
 \c   -i key    private key file for user authentication
@@ -77,7 +79,7 @@ use Plink:
 \c   -agent    enable use of Pageant
 \c   -noshare  disable use of connection sharing
 \c   -share    enable use of connection sharing
-\c   -hostkey aa:bb:cc:...
+\c   -hostkey keyid
 \c             manually specify a host key (may be repeated)
 \c   -sanitise-stderr, -sanitise-stdout, -no-sanitise-stderr, -no-sanitise-stdout
 \c             do/don't strip control chars from standard output/error
@@ -90,6 +92,9 @@ use Plink:
 \c   -sshlog file
 \c   -sshrawlog file
 \c             log protocol details to a file
+\c   -logoverwrite
+\c   -logappend
+\c             control what happens when a log file already exists
 \c   -shareexists
 \c             test whether a connection-sharing upstream exists
 
@@ -114,8 +119,8 @@ characters appearing in your window. Interactive connections like
 this are not the main point of Plink.
 
 In order to connect with a different protocol, you can give the
-command line options \c{-ssh}, \c{-telnet}, \c{-rlogin} or \c{-raw}.
-To make an SSH connection, for example:
+command line options \c{-ssh}, \c{-ssh-connection}, \c{-telnet},
+\c{-rlogin}, or \c{-raw}. To make an SSH connection, for example:
 
 \c C:\>plink -ssh login.example.com
 \c login as:
@@ -163,12 +168,14 @@ key of the server you're connecting to, to enter a user name, or to
 enter a password.
 
 To avoid being prompted for the server host key when using Plink for
-an automated connection, you should first make a \e{manual}
+an automated connection, you can first make a \e{manual}
 connection (using either of PuTTY or Plink) to the same server,
 verify the host key (see \k{gs-hostkey} for more information), and
-select Yes to add the host key to the Registry. After that, Plink
-commands connecting to that server should not give a host key prompt
-unless the host key changes.
+select \q{Accept} to add the host key to the Registry. After that,
+Plink commands connecting to that server should not give a host key
+prompt unless the host key changes. Alternatively, you can specify
+the appropriate host key(s) on Plink's command line every time you
+use it; see \k{using-cmdline-hostkey}.
 
 To avoid being prompted for a user name, you can:
 
@@ -310,26 +317,26 @@ But in case Plink guesses wrong about whether you want this
 sanitisation, you can override it in either direction, using one of
 these options:
 
-\dd \c{-sanitise-stderr}
+\dt \c{-sanitise-stderr}
 
-\dt Sanitise server data written to Plink's standard error channel,
+\dd Sanitise server data written to Plink's standard error channel,
 regardless of terminals and consoles and remote ptys.
 
-\dd \c{-no-sanitise-stderr}
+\dt \c{-no-sanitise-stderr}
 
-\dt Do not sanitise server data written to Plink's standard error
+\dd Do not sanitise server data written to Plink's standard error
 channel.
 
-\dd \c{-sanitise-stdout}
+\dt \c{-sanitise-stdout}
 
-\dt Sanitise server data written to Plink's standard output channel.
+\dd Sanitise server data written to Plink's standard output channel.
 
-\dd \c{-no-sanitise-stdout}
+\dt \c{-no-sanitise-stdout}
 
-\dt Do not sanitise server data written to Plink's standard output
+\dd Do not sanitise server data written to Plink's standard output
 channel.
 
-\S2{plink-option-antispoof} \I{-no-antispoof}: turn off authentication spoofing protection prompt
+\S2{plink-option-antispoof} \i{-no-antispoof}: turn off authentication spoofing protection prompt
 
 In SSH, some possible server authentication methods require user input
 (for example, password authentication, or entering a private key

+ 8 - 3
source/putty/doc/pscp.but

@@ -39,7 +39,7 @@ use PSCP:
 
 \c C:\>pscp
 \c PuTTY Secure Copy client
-\c Release 0.74
+\c Release 0.75
 \c Usage: pscp [options] [user@]host:source target
 \c        pscp [options] source [source...] [user@]host:target
 \c        pscp [options] -ls [user@]host:filespec
@@ -55,12 +55,14 @@ use PSCP:
 \c   -l user   connect with specified username
 \c   -pw passw login with specified password
 \c   -1 -2     force use of particular SSH protocol version
+\c   -ssh -ssh-connection
+\c             force use of particular SSH protocol variant
 \c   -4 -6     force use of IPv4 or IPv6
 \c   -C        enable compression
 \c   -i key    private key file for user authentication
 \c   -noagent  disable use of Pageant
 \c   -agent    enable use of Pageant
-\c   -hostkey aa:bb:cc:...
+\c   -hostkey keyid
 \c             manually specify a host key (may be repeated)
 \c   -batch    disable all interactive prompts
 \c   -no-sanitise-stderr  don't strip control chars from standard error
@@ -72,6 +74,9 @@ use PSCP:
 \c   -sshlog file
 \c   -sshrawlog file
 \c             log protocol details to a file
+\c   -logoverwrite
+\c   -logappend
+\c             control what happens when a log file already exists
 
 (PSCP's interface is much like the Unix \c{scp} command, if you're
 familiar with that.)
@@ -252,7 +257,7 @@ scripts: using \c{-batch}, if something goes wrong at connection
 time, the batch job will fail rather than hang.
 
 \S2{pscp-usage-options-backend}\i\c{-sftp}, \i\c{-scp} force use of
-particular protocol
+particular file transfer protocol
 
 As mentioned in \k{pscp-usage-basics}, there are two different file
 transfer protocols in use with SSH. Despite its name, PSCP (like many

+ 124 - 8
source/putty/doc/pubkey.but

@@ -64,7 +64,7 @@ The key types supported by PuTTY are described in \k{puttygen-keytype}.
 PuTTYgen is a key generator. It \I{generating keys}generates pairs of
 public and private keys to be used with PuTTY, PSCP, and Plink, as well
 as the PuTTY authentication agent, Pageant (see \k{pageant}).  PuTTYgen
-generates RSA, DSA, ECDSA, and Ed25519 keys.
+generates RSA, DSA, ECDSA, and EdDSA keys.
 
 When you run PuTTYgen you will see a window where you have two main
 choices: \q{Generate}, to generate a new public/private key pair, or
@@ -108,7 +108,8 @@ Before generating a key pair using PuTTYgen, you need to select
 which type of key you need.
 
 The current version of the SSH protocol, SSH-2, supports several
-different key types. PuTTYgen can generate:
+different key types, although specific servers may not support all of
+them. PuTTYgen can generate:
 
 \b An \i{RSA} key for use with the SSH-2 protocol.
 
@@ -117,8 +118,8 @@ different key types. PuTTYgen can generate:
 \b An \i{ECDSA} (\i{elliptic curve} DSA) key for use with the
 SSH-2 protocol.
 
-\b An \i{Ed25519} key (another elliptic curve algorithm) for use
-with the SSH-2 protocol.
+\b An \i{EdDSA} key (Edwards-curve DSA, another elliptic curve
+algorithm) for use with the SSH-2 protocol.
 
 PuTTYgen can also generate an RSA key suitable for use with the old
 SSH-1 protocol (which only supports RSA); for this, you need to select
@@ -130,14 +131,64 @@ considered secure, it's rare to need this option.
 The \q{Number of bits} input box allows you to choose the strength
 of the key PuTTYgen will generate.
 
-\b For RSA, 2048 bits should currently be sufficient for most purposes.
-
-\#{FIXME: advice for DSA?}
+\b For RSA and DSA, 2048 bits should currently be sufficient for most
+purposes.
 
 \b For ECDSA, only 256, 384, and 521 bits are supported. (ECDSA offers
 equivalent security to RSA with smaller key sizes.)
 
-\b For Ed25519, the only valid size is 256 bits.
+\b For EdDSA, the only valid sizes are 255 bits (these keys are also
+known as \q{\i{Ed25519}} and are commonly used) and 448 bits
+(\q{\i{Ed448}}, which is much less common at the time of writing).
+(256 is also accepted for backward compatibility, but the effect is
+the same as 255.)
+
+\S{puttygen-primes} Selecting the \i{prime generation method}
+
+On the \q{Key} menu, you can also optionally change the method for
+generating the prime numbers used in the generated key. This is used
+for RSA and DSA keys only. (The other key types don't require
+generating prime numbers at all.)
+
+The prime-generation method does not affect compatibility: a key
+generated with any of these methods will still work with all the same
+SSH servers.
+
+If you don't care about this, it's entirely sensible to leave it on the
+default setting.
+
+The available methods are:
+
+\b Use \i{probable primes} (fast)
+
+\b Use \i{proven primes} (slower)
+
+\b Use proven primes with even distribution (slowest)
+
+The \q{probable primes} method sounds unsafe, but it's the most
+commonly used prime-generation strategy. There is in theory a
+possibility that it might accidentally generate a number that isn't
+prime, but the software does enough checking to make that probability
+vanishingly small (less than 1 in 2^80, or 1 in 10^24). So, in
+practice, nobody worries about it very much.
+
+The other methods cause PuTTYgen to use numbers that it is \e{sure}
+are prime, because it generates the output number together with a
+proof of its primality. This takes more effort, but it eliminates that
+theoretical risk in the probabilistic method.
+
+You might choose to switch from probable to proven primes if you have
+a local security standard that demands it, or if you don't trust the
+probabilistic argument for the safety of the usual method.
+
+For RSA keys, there's also an option on the \q{Key} menu to use
+\i{\q{strong} primes} as the prime factors of the public key. A \q{strong}
+prime is a prime number chosen to have a particular structure that
+makes certain factoring algorithms more difficult to apply, so some
+security standards recommend their use. However, the most modern
+factoring algorithms are unaffected, so this option is probably not
+worth turning on \e{unless} you have a local standard that recommends
+it.
 
 \S{puttygen-generate} The \q{Generate} button
 
@@ -179,6 +230,13 @@ a particular fingerprint. So some utilities, such as the Pageant key
 list box (see \k{pageant-mainwin-keylist}) and the Unix \c{ssh-add}
 utility, will list key fingerprints rather than the whole public key.
 
+By default, PuTTYgen will display fingerprints in the \q{SHA256}
+format. If you need to see the fingerprint in the older \q{MD5} format
+(which looks like \c{aa:bb:cc:...}), you can choose
+\q{Show fingerprint as MD5} from the \q{Key} menu, but bear in mind
+that this is less cryptographically secure; it may be feasible for
+an attacker to create a key with the same fingerprint as yours.
+
 \S{puttygen-comment} Setting a comment for your key
 
 If you have more than one key and use them for different purposes,
@@ -254,6 +312,10 @@ will need to tell PuTTY to use for authentication (see
 \k{config-ssh-privkey}) or tell Pageant to load (see
 \k{pageant-mainwin-addkey}).
 
+(You can optionally change some details of the PPK format for your saved
+key files; see \k{puttygen-save-params}. But The defaults should be
+fine for most purposes.)
+
 \S{puttygen-savepub} Saving your public key to a disk file
 
 RFC 4716 specifies a \I{SSH-2 public key format}standard format for
@@ -293,6 +355,60 @@ PuTTY session which is already connected to the server.
 See \k{pubkey-gettingready} for general instructions on configuring
 public-key authentication once you have generated a key.
 
+\S{puttygen-save-params} Parameters for saving key files
+
+Selecting \q{Parameters for saving key files...} from the \q{Key} menu
+lets you adjust some aspects of PPK-format private key files stored on
+disk. None of these options affect compatibility with SSH servers.
+
+In most cases, it's entirely sensible to leave all of these at their
+default settings.
+
+\S2{puttygen-save-ppk-version} PPK file version
+
+This defaults to version 3, which is fine for most uses.
+
+You might need to select PPK version 2 if you need your private key
+file to be loadable in older versions of PuTTY (0.74 and older), or in
+other tools which do not yet support the version 3 format (which was
+introduced in 2021).
+
+The version 2 format is less resistant to brute-force decryption, and
+doesn't support any of the following options to control that.
+
+\S2{puttygen-save-passphrase-hashing} Options affecting \i{passphrase hashing}
+
+All of the following options only affect keys saved with passphrases.
+They control how much work is required to decrypt the key (which
+happens every time you type its passphrase). This allows you to trade
+off the cost of legitimate use of the key against the resistance of
+the encrypted key to password-guessing attacks.
+
+These options only affect PPK version 3.
+
+\dt Key derivation function
+
+\dd The variant of the \i{Argon2} key derivation function to use.
+You might change this if you consider your exposure to side-channel
+attacks to be different to the norm.
+
+\dt Memory to use for passphrase hash
+
+\dd The amount of memory needed to decrypt the key, in Kbyte.
+
+\dt Time to use for passphrase hash
+
+\dd Controls how much time is required to attempt decrypting the key.
+You can either specify an approximate time in milliseconds (on this
+machine), or explicitly specify a number of hash passes (which is what
+the time is turned into during encryption).
+
+\dt Parallelism for passphrase hash
+
+\dd Number of parallelisable threads that can be used to decrypt the
+key. The default, 1, forces the process to run single-threaded, even
+on machines with multiple cores.
+
 \S{puttygen-load} Reloading a private key
 
 PuTTYgen allows you to load an existing private key file into

+ 408 - 0
source/putty/doc/pubkeyfmt.but

@@ -0,0 +1,408 @@
+\A{ppk} PPK file format
+
+This appendix documents the file format used by PuTTY to store private
+keys.
+
+In this appendix, binary data structures are described using data type
+representations such as \cq{uint32}, \cq{string} and \cq{mpint} as
+used in the SSH protocol standards themselves. These are defined
+authoritatively by
+\W{https://tools.ietf.org/html/rfc4251#section-5}{RFC 4251 section 5},
+\q{Data Type Representations Used in the SSH Protocols}.
+
+\H{ppk-overview} Overview
+
+A PPK file stores a private key, and the corresponding public key.
+Both are contained in the same file.
+
+The file format can be completely unencrypted, or it can encrypt the
+private key. The \e{public} key is stored in cleartext in both cases.
+(This enables PuTTY to send the public key to an SSH server to see
+whether it will accept it, and not bother prompting for the passphrase
+unless the server says yes.)
+
+When the key file is encrypted, the encryption key is derived from a
+passphrase. An encrypted PPK file is also tamper-proofed using a MAC
+(authentication code), also derived from the same passphrase. The MAC
+protects the encrypted private key data, but it also covers the
+cleartext parts of the file. So you can't edit the public half of the
+key without invalidating the MAC and causing the key file as a whole
+to become useless.
+
+This MAC protects the key file against active cryptographic attacks in
+which the public half of a key pair is modified in a controlled way
+that allows an attacker to deduce information about the private half
+from the resulting corrupted signatures. Any attempt to do that to a
+PPK file should be reliably caught by the MAC failing to validate.
+
+(Such an attack would only be useful if the key file was stored in a
+location where the attacker could modify it without also having full
+access to the process that you type passphrases into. But that's not
+impossible; for example, if your home directory was on a network file
+server, then the file server's administrator could access the key file
+but not processes on the client machine.)
+
+The MAC also covers the \e{comment} on the key. This stops an attacker
+from swapping keys with each other and editing the comments to
+disguise the fact. As a consequence, PuTTYgen cannot edit the comment
+on a key unless you decrypt the key with your passphrase first.
+
+(The circumstances in which \e{that} attack would be useful are even
+more restricted. One example might be that the different keys trigger
+specific actions on the server you're connecting to and one of those
+actions is more useful to the attacker than the other. But once you
+have a MAC at all, it's no extra effort to make it cover as much as
+possible, and usually sensible.)
+
+\H{ppk-outer} Outer layer
+
+The outer layer of a PPK file is text-based. The PuTTY tools will
+always use LF line termination when writing PPK files, but will
+tolerate CR+LF and CR-only on input.
+
+The first few lines identify it as a PPK, and give some initial data
+about what's stored in it and how. They look like this:
+
+\c PuTTY-User-Key-File-version: algorithm-name
+\e                     bbbbbbb  bbbbbbbbbbbbbb
+\c Encryption: encryption-type
+\e             bbbbbbbbbbbbbbb
+\c Comment: key-comment-string
+\e          bbbbbbbbbbbbbbbbbb
+
+\s{version} is a decimal number giving the version number of the file
+format itself. The current file format version is 3.
+
+\s{algorithm-name} is the SSH protocol identifier for the public key
+algorithm that this key is used for (such as \cq{ssh-dss} or
+\cq{ecdsa-sha2-nistp384}).
+
+\s{encryption-type} indicates whether this key is stored encrypted,
+and if so, by what method. Currently the only supported encryption
+types are \cq{aes256-cbc} and \cq{none}.
+
+\s{key-comment-string} is a free text field giving the comment. This
+can contain any byte values other than 13 and 10 (CR and LF).
+
+The next part of the file gives the public key. This is stored
+unencrypted but base64-encoded
+(\W{https://tools.ietf.org/html/rfc4648}{RFC 4648}), and is preceded
+by a header line saying how many lines of base64 data are shown,
+looking like this:
+
+\c Public-Lines: number-of-lines
+\e               bbbbbbbbbbbbbbb
+\c that many lines of base64 data
+\e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+
+The base64-encoded data in this blob is formatted in exactly the same
+way as an SSH public key sent over the wire in the SSH protocol
+itself. That is also the same format as the base64 data stored in
+OpenSSH's \c{authorized_keys} file, except that in a PPK file the
+base64 data is split across multiple lines. But if you remove the
+newlines from the middle of this section, the resulting base64 blob is
+in the right format to go in an \c{authorized_keys} line.
+
+If the key is encrypted (i.e. \s{encryption-type} is not \cq{none}),
+then the next thing that appears is a sequence of lines specifying how
+the keys for encrypting the file are to be derived from the
+passphrase:
+
+\c Key-Derivation: argon2-flavour
+\e                 bbbbbbbbbbbbbb
+\c Argon2-Memory: decimal-integer
+\e                bbbbbbbbbbbbbbb
+\c Argon2-Passes: decimal-integer
+\e                bbbbbbbbbbbbbbb
+\c Argon2-Parallelism: decimal-integer
+\e                     bbbbbbbbbbbbbbb
+\c Argon2-Salt: hex-string
+\e              bbbbbbbbbb
+
+\s{argon2-flavour} is one of the identifiers \cq{Argon2d},
+\cq{Argon2i} or \cq{Argon2id}, all describing variants of the Argon2
+password-hashing function.
+
+The three integer values are used as parameters for Argon2, which
+allows you to configure the amount of memory used (in Kbyte), the number
+of passes of the algorithm to run (to tune its running time), and the
+degree of parallelism required by the hash function. The salt is
+decoded into a sequence of binary bytes and used as an additional
+input to Argon2. (It is chosen randomly when the key file is written,
+so that a guessing attack can't be mounted in parallel against
+multiple key files.)
+
+The next part of the file gives the private key. This is
+base64-encoded in the same way:
+
+\c Private-Lines: number-of-lines
+\e                bbbbbbbbbbbbbbb
+\c that many lines of base64 data
+\e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+
+The binary data represented in this base64 blob may be encrypted,
+depending on the \e{encryption-type} field in the key file header
+shown above:
+
+\b If \s{encryption-type} is \cq{none}, then this data is stored in
+plain text.
+
+\b If \s{encryption-type} is \cq{aes256-cbc}, then this data is
+encrypted using AES, with a 256-bit key length, in the CBC cipher
+mode. The key and initialisation vector are derived from the
+passphrase: see \k{ppk-keys}.
+
+\lcont{
+
+In order to encrypt the private key data with AES, it must be a
+multiple of 16 bytes (the AES cipher block length). This is achieved
+by appending random padding to the data before encrypting it. When
+decoding it after decryption, the random data can be ignored: the
+internal structure of the data is enough to tell you when you've
+reached the end of the meaningful part.
+
+}
+
+Unlike public keys, the binary encoding of private keys is not
+specified at all in the SSH standards. See \k{ppk-privkeys} for
+details of the private key format for each key type supported by
+PuTTY.
+
+The final thing in the key file is the MAC:
+
+\c Private-MAC: hex-mac-data
+\e              bbbbbbbbbbbb
+
+\s{hex-mac-data} is a hexadecimal-encoded value, 64 digits long (i.e.
+32 bytes), generated using the HMAC-SHA-256 algorithm with the
+following binary data as input:
+
+\b \cw{string}: the \s{algorithm-name} header field.
+
+\b \cw{string}: the \s{encryption-type} header field.
+
+\b \cw{string}: the \s{key-comment-string} header field.
+
+\b \cw{string}: the binary public key data, as decoded from the base64
+lines after the \cq{Public-Lines} header.
+
+\b \cw{string}: the plaintext of the binary private key data, as
+decoded from the base64 lines after the \cq{Private-Lines} header. If
+that data was stored encrypted, then the decrypted version of it is
+used in this MAC preimage, \e{including} the random padding mentioned
+above.
+
+The MAC key is derived from the passphrase: see \k{ppk-keys}.
+
+\H{ppk-privkeys} Private key encodings
+
+This section describes the private key format for each key type
+supported by PuTTY.
+
+Because the PPK format also contains the public key (and both public
+and private key are protected by the same MAC to ensure they can't be
+made inconsistent), there is no need for the private key section of
+the file to repeat data from the public section. So some of these
+formats are very short.
+
+In all cases, a decoding application can begin reading from the start
+of the decrypted private key data, and know when it has read all that
+it needs. This allows random padding after the meaningful data to be
+safely ignored.
+
+\S{ppk-privkey-rsa} RSA
+
+RSA keys are stored using an \s{algorithm-name} of \cq{ssh-rsa}. (Keys
+stored like this are also used by the updated RSA signature schemes
+that use hashes other than SHA-1.)
+
+The public key data has already provided the key modulus and the
+public encoding exponent. The private data stores:
+
+\b \cw{mpint}: the private decoding exponent of the key.
+
+\b \cw{mpint}: one prime factor \e{p} of the key.
+
+\b \cw{mpint}: the other prime factor \e{q} of the key. (RSA keys
+stored in this format are expected to have exactly two prime factors.)
+
+\b \cw{mpint}: the multiplicative inverse of \e{q} modulo \e{p}.
+
+\S{ppk-privkey-dsa} DSA
+
+DSA keys are stored using an \s{algorithm-name} of \cq{ssh-dss}.
+
+The public key data has already provided the key parameters (the large
+prime \e{p}, the small prime \e{q} and the group generator \e{g}), and
+the public key \e{y}. The private key stores:
+
+\b \cw{mpint}: the private key \e{x}, which is the discrete logarithm
+of \e{y} in the group generated by \e{g} mod \e{p}.
+
+\S{ppk-privkey-ecdsa} NIST elliptic-curve keys
+
+NIST elliptic-curve keys are stored using one of the following
+\s{algorithm-name} values, each corresponding to a different elliptic
+curve and key size:
+
+\b \cq{ecdsa-sha2-nistp256}
+
+\b \cq{ecdsa-sha2-nistp384}
+
+\b \cq{ecdsa-sha2-nistp521}
+
+The public key data has already provided the public elliptic curve
+point. The private key stores:
+
+\b \cw{mpint}: the private exponent, which is the discrete log of the
+public point.
+
+\S{ppk-privkey-eddsa} EdDSA elliptic-curve keys (Ed25519 and Ed448)
+
+EdDSA elliptic-curve keys are stored using one of the following
+\s{algorithm-name} values, each corresponding to a different elliptic
+curve and key size:
+
+\b \cq{ssh-ed25519}
+
+\b \cq{ssh-ed448}
+
+The public key data has already provided the public elliptic curve
+point. The private key stores:
+
+\b \cw{mpint}: the private exponent, which is the discrete log of the
+public point.
+
+\H{ppk-keys} Key derivation
+
+When a key file is encrypted, there are three pieces of key material
+that need to be computed from the passphrase:
+
+\b the key for the symmetric cipher used to encrypt the private key
+
+\b the initialisation vector for that cipher encryption
+
+\b the key for the MAC.
+
+If \s{encryption-type} is \cq{aes256-cbc}, then the symmetric cipher
+key is 32 bytes long, and the initialisation vector is 16 bytes (one
+cipher block). The length of the MAC key is also chosen to be 32
+bytes.
+
+If \s{encryption-type} is \cq{none}, then all three of these pieces of
+data have zero length. (The MAC is still generated and checked in the
+key file format, but it has a zero-length key.)
+
+If the amount of key material required is not zero, then the
+passphrase is fed to the Argon2 key derivation function, in whichever
+mode is described in the \cq{Key-Derivation} header in the key file,
+with parameters derived from the various
+\q{\cw{Argon2-}\e{Parameter}\cw{:}} headers.
+
+(If the key is unencrypted, then all those headers are omitted, and
+Argon2 is not run at all.)
+
+Argon2 takes two extra string inputs in addition to the passphrase and
+the salt: a secret key, and some \q{associated data}. In PPK's use of
+Argon2, these are both set to the empty string.
+
+The \q{tag length} parameter to Argon2 (i.e. the amount of data it is
+asked to output) is set to the sum of the lengths of all of the data
+items required, i.e. (cipher key length + IV length + MAC key length).
+The output data is interpreted as the concatenation of the cipher key,
+the IV and the MAC key, in that order.
+
+So, for \cq{aes256-cbc}, the tag length will be 32+16+32\_=\_80 bytes;
+of the 80 bytes of output data, the first 32 bytes are used as the
+256-bit AES key, the next 16 as the CBC IV, and the final 32 bytes as
+the HMAC-SHA-256 key.
+
+\H{ppk-old} Older versions of the PPK format
+
+\S{ppk-v2} Version 2
+
+PPK version 2 was used by PuTTY 0.52 to 0.74 inclusive.
+
+In PPK version 2, the MAC algorithm used was HMAC-SHA-1 (so the
+\cw{Private-MAC} line contained only 40 hex digits).
+
+The \cq{Key-Derivation:} header and all the
+\q{\cw{Argon2-}\e{Parameter}\cw{:}} headers were absent. Instead of
+using Argon2, the key material for encrypting the private blob was
+derived from the passphrase in a totally different way, as follows.
+
+The cipher key for \cq{aes256-cbc} was constructed by generating two
+SHA-1 hashes, concatenating them, and taking the first 32 bytes of the
+result. (So you'd get all 20 bytes of the first hash output, and the
+first 12 of the second). Each hash preimage was as follows:
+
+\b \cw{uint32}: a sequence number. This is 0 in the first hash, and 1
+in the second. (The idea was to extend this mechanism to further
+hashes by continuing to increment the sequence number, if future
+changes required even longer keys.)
+
+\b the passphrase, without any prefix length field.
+
+In PPK v2, the CBC initialisation vector was all zeroes.
+
+The MAC key was 20 bytes long, and was a single SHA-1 hash of the
+following data:
+
+\b the fixed string \cq{putty-private-key-file-mac-key}, without any
+prefix length field.
+
+\b the passphrase, without any prefix length field. (If the key is
+stored unencrypted, the passphrase was taken to be the empty string
+for these purposes.)
+
+\S{ppk-v1} Version 1
+
+PPK version 1 was a badly designed format, only used during initial
+development, and not recommended for production use.
+
+PPK version 1 was never used by a released version of PuTTY. It was
+only emitted by some early development snapshots between version 0.51
+(which did not support SSH-2 public keys at all) and 0.52 (which
+already used version 2 of this file format). I \e{hope} there are no
+PPK v1 files in use anywhere. But just in case, the old badly designed
+format is documented here anyway.
+
+In PPK version 1, the input to the MAC does not include any of the
+header fields or the public key. It is simply the private key data
+(still in plaintext and including random padding), all by itself
+(without a wrapping \cw{string}).
+
+PPK version 1 keys must therefore be rigorously validated after
+loading, to ensure that the public and private parts of the key were
+consistent with each other.
+
+PPK version 1 only supported the RSA and DSA key types. For RSA, this
+validation can be done using only the provided data (since the private
+key blob contains enough information to reconstruct the public values
+anyway). But for DSA, that isn't quite enough.
+
+Hence, PPK version 1 DSA keys extended the private data so that
+immediately after \e{x} was stored an extra value:
+
+\b \cw{string}: a SHA-1 hash of the public key data, whose preimage
+consists of
+
+\lcont{
+
+\b \cw{string}: the large prime \e{p}
+
+\b \cw{string}: the small prime \e{q}
+
+\b \cw{string}: the group generator \e{g}
+
+}
+
+The idea was that checking this hash would verify that the key
+parameters had not been tampered with, and then the loading
+application could directly verify that
+\e{g}\cw{^}\e{x}\cw{\_=\_}\e{y}.
+
+In an \e{unencrypted} version 1 key file, the MAC is replaced by a
+plain SHA-1 hash of the private key data. This is indicated by the
+\cq{Private-MAC:} header being replaced with \cq{Private-Hash:}
+instead.

+ 63 - 0
source/putty/doc/sshnames.but

@@ -65,3 +65,66 @@ They have been superseded by \cw{rsa1024-sha1} and \cw{rsa2048-sha256}.
 
 \dd These were used in drafts of what eventually became RFC\_4345.
 They have been superseded by \cw{arcfour128} and \cw{arcfour256}.
+
+\H{sshnames-agent} Agent extension request names
+
+The SSH agent protocol, which is only specified in an Internet-Draft
+at the time of writing
+(\W{https://tools.ietf.org/html/draft-miller-ssh-agent}\cw{draft-miller-ssh-agent}),
+defines an extension mechanism. These names can be sent in an
+\cw{SSH_AGENTC_EXTENSION} message.
+
+\dt \cw{[email protected]}
+
+\dd The payload is a single SSH-2 \cw{string} containing a keypair in
+the PPK format defined in \k{ppk}. Compared to the standard
+\cw{SSH_AGENTC_ADD_IDENTITY}, this extension allows adding keys in
+encrypted form, with the agent requesting a decryption passphrase from
+the user on demand, and able to revert the key to encrypted form.
+
+\dt \cw{[email protected]}
+
+\dd The payload is a single SSH-2 \cw{string} specifying a public key
+blob, as in \cw{SSH_AGENTC_REMOVE_IDENTITY}. Requests that the agent
+forget any cleartext form of a specific key. 
+
+\lcont{
+Returns \cw{SSH_AGENT_SUCCESS} if the agent ended up holding the key
+only in encrypted form (even if it was already encrypted); returns
+\cw{SSH_AGENT_EXTENSION_FAILURE} if not (if it wasn't held by the
+agent at all, or only in cleartext form).
+}
+
+\dt \cw{[email protected]}
+
+\dd No payload. Requests that the agent forget the cleartext form of
+any keys for which it holds an encrypted form.
+
+\lcont{
+If the agent holds any keys with an encrypted form (or no keys at all),
+returns \cw{SSH_AGENT_SUCCESS} to indicate that no such keys are now
+held in cleartext form, followed by a \cw{uint32} specifying how many keys
+remain in cleartext form (because the agent didn't hold an encrypted
+form for them). If the agent holds nothing but keys in cleartext form,
+returns \cw{SSH_AGENT_EXTENSION_FAILURE}.
+}
+
+\dt \cw{[email protected]}
+
+\dd No payload. Returns \cw{SSH_AGENT_SUCCESS} followed by a list of
+identities similar to \cw{SSH_AGENT_IDENTITIES_ANSWER}, except that
+each key has an extra SSH-2 \cw{string} at the end. Currently that
+\cw{string} contains a single \cw{uint32} flags word, with the
+following bits defined:
+
+\lcont{
+\dt Bit 0
+
+\dd If set, key is held with an encrypted form (so that the
+\c{reencrypt} extension can do something useful with it).
+
+\dt Bit 1
+
+\dd If set, key's cleartext form is not currently held (so the
+user will have to supply a passphrase before the key can be used).
+}

+ 385 - 6
source/putty/doc/udp.but

@@ -142,12 +142,11 @@ potentially managing multiple sessions.
 Therefore, the platform-independent parts of PuTTY never use global
 variables to store per-session data. The global variables that do
 exist are tolerated because they are not specific to a particular
-login session: \c{flags} defines properties that are expected to
-apply equally to \e{all} the sessions run by a single PuTTY process,
-the random number state in \cw{sshrand.c} and the timer list in
-\cw{timing.c} serve all sessions equally, and so on. But most data
-is specific to a particular network session, and is therefore stored
-in dynamically allocated data structures, and pointers to these
+login session. The random number state in \cw{sshrand.c}, the timer
+list in \cw{timing.c} and the queue of top-level callbacks in
+\cw{callback.c} serve all sessions equally. But most data is specific
+to a particular network session, and is therefore stored in
+dynamically allocated data structures, and pointers to these
 structures are passed around between functions.
 
 Platform-specific code can reverse this decision if it likes. The
@@ -412,6 +411,386 @@ ensure that when you free that memory you reset the pointer field to
 is called, it can reliably free the memory if there is any, and not
 crash if there isn't.
 
+\H{udp-traits} Explicit vtable structures to implement traits
+
+A lot of PuTTY's code is written in a style that looks structurally
+rather like an object-oriented language, in spite of PuTTY being a
+pure C program.
+
+For example, there's a single data type called \cw{ssh_hash}, which is
+an abstraction of a secure hash function, and a bunch of functions
+called things like \cw{ssh_hash_}\e{foo} that do things with those
+data types. But in fact, PuTTY supports many different hash functions,
+and each one has to provide its own implementation of those functions.
+
+In C++ terms, this is rather like having a single abstract base class,
+and multiple concrete subclasses of it, each of which fills in all the
+pure virtual methods in a way that's compatible with the data fields
+of the subclass. The implementation is more or less the same, as well:
+in C, we do explicitly in the source code what the C++ compiler will
+be doing behind the scenes at compile time.
+
+But perhaps a closer analogy in functional terms is the Rust concept
+of a \q{trait}, or the Java idea of an \q{interface}. C++ supports a
+multi-level hierarchy of inheritance, whereas PuTTY's system \dash
+like traits or interfaces \dash has only two levels, one describing a
+generic object of a type (e.g. a hash function) and another describing
+a specific implementation of that type (e.g. SHA-256).
+
+The PuTTY code base has a standard idiom for doing this in C, as
+follows.
+
+Firstly, we define two \cw{struct} types for our trait. One of them
+describes a particular \e{kind} of implementation of that trait, and
+it's full of (mostly) function pointers. The other describes a
+specific \e{instance} of an implementation of that trait, and it will
+contain a pointer to a \cw{const} instance of the first type. For
+example:
+
+\c typedef struct MyAbstraction MyAbstraction;
+\c typedef struct MyAbstractionVtable MyAbstractionVtable;
+\c
+\c struct MyAbstractionVtable {
+\c     MyAbstraction *(*new)(const MyAbstractionVtable *vt);
+\c     void (*free)(MyAbstraction *);
+\c     void (*modify)(MyAbstraction *, unsigned some_parameter);
+\c     unsigned (*query)(MyAbstraction *, unsigned some_parameter);
+\c };
+\c
+\c struct MyAbstraction {
+\c     const MyAbstractionVtable *vt;
+\c };
+
+Here, we imagine that \cw{MyAbstraction} might be some kind of object
+that contains mutable state. The associated vtable structure shows
+what operations you can perform on a \cw{MyAbstraction}: you can
+create one (dynamically allocated), free one you already have, or call
+the example methods \q{modify} (to change the state of the object in
+some way) and \q{query} (to return some value derived from the
+object's current state).
+
+(In most cases, the vtable structure has a name ending in \cq{vtable}.
+But for historical reasons a lot of the crypto primitives that use
+this scheme \dash ciphers, hash functions, public key methods and so
+on \dash instead have names ending in \cq{alg}, on the basis that the
+primitives they implement are often referred to as \q{encryption
+algorithms}, \q{hash algorithms} and so forth.)
+
+Now, to define a concrete instance of this trait, you'd define a
+\cw{struct} that contains a \cw{MyAbstraction} field, plus any other
+data it might need:
+
+\c struct MyImplementation {
+\c     unsigned internal_data[16];
+\c     SomeOtherType *dynamic_subthing;
+\c
+\c     MyAbstraction myabs;
+\c };
+
+Next, you'd implement all the necessary methods for that
+implementation of the trait, in this kind of style:
+
+\c static MyAbstraction *myimpl_new(const MyAbstractionVtable *vt)
+\c {
+\c     MyImplementation *impl = snew(MyImplementation);
+\c     memset(impl, 0, sizeof(*impl));
+\c     impl->dynamic_subthing = allocate_some_other_type();
+\c     impl->myabs.vt = vt;
+\c     return &impl->myabs;
+\c }
+\c
+\c static void myimpl_free(MyAbstraction *myabs)
+\c {
+\c     MyImplementation *impl = container_of(myabs, MyImplementation, myabs);
+\c     free_other_type(impl->dynamic_subthing);
+\c     sfree(impl);
+\c }
+\c
+\c static void myimpl_modify(MyAbstraction *myabs, unsigned param)
+\c {
+\c     MyImplementation *impl = container_of(myabs, MyImplementation, myabs);
+\c     impl->internal_data[param] += do_something_with(impl->dynamic_subthing);
+\c }
+\c
+\c static unsigned myimpl_query(MyAbstraction *myabs, unsigned param)
+\c {
+\c     MyImplementation *impl = container_of(myabs, MyImplementation, myabs);
+\c     return impl->internal_data[param];
+\c }
+
+Having defined those methods, now we can define a \cw{const} instance
+of the vtable structure containing pointers to them:
+
+\c const MyAbstractionVtable MyImplementation_vt = {
+\c     .new = myimpl_new,
+\c     .free = myimpl_free,
+\c     .modify = myimpl_modify,
+\c     .query = myimpl_query,
+\c };
+
+\e{In principle}, this is all you need. Client code can construct a
+new instance of a particular implementation of \cw{MyAbstraction} by
+digging out the \cw{new} method from the vtable and calling it (with
+the vtable itself as a parameter), which returns a \cw{MyAbstraction
+*} pointer that identifies a newly created instance, in which the
+\cw{vt} field will contain a pointer to the same vtable structure you
+passed in. And once you have an instance object, say \cw{MyAbstraction
+*myabs}, you can dig out one of the other method pointers from the
+vtable it points to, and call that, passing the object itself as a
+parameter.
+
+But in fact, we don't do that, because it looks pretty ugly at all the
+call sites. Instead, what we generally do in this code base is to
+write a set of \cw{static inline} wrapper functions in the same header
+file that defined the \cw{MyAbstraction} structure types, like this:
+
+\c static MyAbstraction *myabs_new(const MyAbstractionVtable *vt)
+\c { return vt->new(vt); }
+\c static void myabs_free(MyAbstraction *myabs)
+\c { myabs->vt->free(myabs); }
+\c static void myimpl_modify(MyAbstraction *myabs, unsigned param)
+\c { myabs->vt->modify(myabs, param); }
+\c static unsigned myimpl_query(MyAbstraction *myabs, unsigned param)
+\c { return myabs->vt->query(myabs, param); }
+
+And now call sites can use those reasonably clean-looking wrapper
+functions, and shouldn't ever have to directly refer to the \cw{vt}
+field inside any \cw{myabs} object they're holding. For example, you
+might write something like this:
+
+\c MyAbstraction *myabs = myabs_new(&MyImplementation_vtable);
+\c myabs_update(myabs, 10);
+\c unsigned output = myabs_query(myabs, 2);
+\c myabs_free(myabs);
+
+and then all this code can use a different implementation of the same
+abstraction by just changing which vtable pointer it passed in in the
+first line.
+
+Some things to note about this system:
+
+\b The implementation instance type (here \cq{MyImplementation}
+contains the abstraction type (\cq{MyAbstraction}) as one of its
+fields. But that field is not necessarily at the start of the
+structure. So you can't just \e{cast} pointers back and forth between
+the two types. Instead:
+
+\lcont{
+
+\b You \q{up-cast} from implementation to abstraction by taking the
+address of the \cw{MyAbstraction} field. You can see the example
+\cw{new} method above doing this, returning \cw{&impl->myabs}. All
+\cw{new} methods do this on return.
+
+\b Going in the other direction, each method that was passed a generic
+\cw{MyAbstraction *myabs} parameter has to recover a pointer to the
+specific implementation type \cw{MyImplementation *impl}. The idiom
+for doing that is to use the \cq{container_of} macro, also seen in the
+Linux kernel code. Generally, \cw{container_of(p, Type, field)} says:
+\q{I'm confident that the pointer value \cq{p} is pointing to the
+field called \cq{field} within a larger \cw{struct} of type \cw{Type}.
+Please return me the pointer to the containing structure.} So in this
+case, we take the \cq{myabs} pointer passed to the function, and
+\q{down-cast} it into a pointer to the larger and more specific
+structure type \cw{MyImplementation}, by adjusting the pointer value
+based on the offset within that structure of the field called
+\cq{myabs}.
+
+This system is flexible enough to permit \q{multiple inheritance}, or
+rather, multiple \e{implementation}: having one object type implement
+more than one trait. For example, the \cw{Proxy} type implements both
+the \cw{Socket} trait and the \cw{Plug} trait that connects to it,
+because it has to act as an adapter between another instance of each
+of those types.
+
+It's also perfectly possible to have the same object implement the
+\e{same} trait in two different ways. At the time of writing this I
+can't think of any case where we actually do this, but a theoretical
+example might be if you needed to support a trait like \cw{Comparable}
+in two ways that sorted by different criteria. There would be no
+difficulty doing this in the PuTTY system: simply have your
+implementation \cw{struct} contain two (or more) fields of the same
+abstraction type. The fields will have different names, which makes it
+easy to explicitly specify which one you're returning a pointer to
+during up-casting, or which one you're down-casting from using
+\cw{container_of}. And then both sets of implementation methods can
+recover a pointer to the same containing structure.
+
+}
+
+\b Unlike in C++, all objects in PuTTY that use this system are
+dynamically allocated. The \q{constructor} functions (whether they're
+virtualised across the whole abstraction or specific to each
+implementation) always allocate memory and return a pointer to it. The
+\q{free} method (our analogue of a destructor) always expects the
+input pointer to be dynamically allocated, and frees it. As a result,
+client code doesn't need to know how large the implementing object
+type is, because it will never need to allocate it (on the stack or
+anywhere else).
+
+\b Unlike in C++, the abstraction's \q{vtable} structure does not only
+hold methods that you can call on an instance object. It can also
+hold several other kinds of thing:
+
+\lcont{
+
+\b Methods that you can call \e{without} an instance object, given
+only the vtable structure identifying a particular implementation of
+the trait. You might think of these as \q{static methods}, as in C++,
+except that they're \e{virtual} \dash the same code can call the
+static method of a different \q{class} given a different vtable
+pointer. So they're more like \q{virtual static methods}, which is a
+concept C++ doesn't have. An example is the \cw{pubkey_bits} method in
+\cw{ssh_keyalg}.
+
+\b The most important case of a \q{virtual static method} is the
+\cw{new} method that allocates and returns a new object. You can think
+of it as a \q{virtual constructor} \dash another concept C++ doesn't
+have. (However, not all types need one of these: see below.)
+
+\b The vtable can also contain constant data relevant to the class as
+a whole \dash \q{virtual constant data}. For example, a cryptographic
+hash function will contain an integer field giving the length of the
+output hash, and most crypto primitives will contain a string field
+giving the identifier used in the SSH protocol that describes that
+primitive.
+
+The effect of all of this is that you can make other pieces of code
+able to use any instance of one of these types, by passing it an
+actual vtable as a parameter. For example, the \cw{hash_simple}
+function takes an \cw{ssh_hashalg} vtable pointer specifying any hash
+algorithm you like, and internally, it creates an object of that type,
+uses it, and frees it. In C++, you'd probably do this using a
+template, which would mean you had multiple specialisations of
+\cw{hash_simple} \dash and then it would be much more difficult to
+decide \e{at run time} which one you needed to use. Here,
+\cw{hash_simple} is still just one function, and you can decide as
+late as you like which vtable to pass to it.
+
+}
+
+\b The abstract \e{instance} structure can also contain publicly
+visible data fields (this time, usually treated as mutable) which are
+common to all implementations of the trait. For example,
+\cw{BinaryPacketProtocol} has lots of these.
+
+\b Not all abstractions of this kind want virtual constructors. It
+depends on how different the implementations are.
+
+\lcont{
+
+With a crypto primitive like a hash algorithm, the constructor call
+looks the same for every implementing type, so it makes sense to have
+a standardised virtual constructor in the vtable and a
+\cw{ssh_hash_new} wrapper function which can make an instance of
+whatever vtable you pass it. And then you make all the vtable objects
+themselves globally visible throughout the source code, so that any
+module can call (for example) \cw{ssh_hash_new(&ssh_sha256)}.
+
+But with other kinds of object, the constructor for each implementing
+type has to take a different set of parameters. For example,
+implementations of \cw{Socket} are not generally interchangeable at
+construction time, because constructing different kinds of socket
+require totally different kinds of address parameter. In that
+situation, it makes more sense to keep the vtable structure itself
+private to the implementing source file, and instead, publish an
+ordinary constructing function that allocates and returns an instance
+of that particular subtype, taking whatever parameters are appropriate
+to that subtype.
+
+}
+
+\b If you do have virtual constructors, you can choose whether they
+take a vtable pointer as a parameter (as shown above), or an
+\e{existing} instance object. In the latter case, they can refer to
+the object itself as well as the vtable. For example, you could have a
+trait come with a virtual constructor called \q{clone}, meaning
+\q{Make a copy of this object, no matter which implementation it is.}
+
+\b Sometimes, a single vtable structure type can be shared between two
+completely different object types, and contain all the methods for
+both. For example, \cw{ssh_compression_alg} contains methods to
+create, use and free \cw{ssh_compressor} and \cw{ssh_decompressor}
+objects, which are not interchangeable \dash but putting their methods
+in the same vtable means that it's easy to create a matching pair of
+objects that are compatible with each other.
+
+\b Passing the vtable itself as an argument to the \cw{new} method is
+not compulsory: if a given \cw{new} implementation is only used by a
+single vtable, then that function can simply hard-code the vtable
+pointer that it writes into the object it constructs. But passing the
+vtable is more flexible, because it allows a single constructor
+function to be shared between multiple slightly different object
+types. For example, SHA-384 and SHA-512 share the same \cw{new} method
+and the same implementation data type, because they're very nearly the
+same hash algorithm \dash but a couple of the other methods in their
+vtables are different, because the \q{reset} function has to set up
+the initial algorithm state differently, and the \q{digest} method has
+to write out a different amount of data.
+
+\lcont{
+
+One practical advantage of having the \cw{myabs_}\e{foo} family of
+inline wrapper functions in the header file is that if you change your
+mind later about whether the vtable needs to be passed to \cw{new},
+you only have to update the \cw{myabs_new} wrapper, and then the
+existing call sites won't need changing.
+
+}
+
+\b Another piece of \q{stunt object orientation} made possible by this
+scheme is that you can write two vtables that both use the same
+structure layout for the implementation object, and have an object
+\e{transform from one to the other} part way through its lifetime, by
+overwriting its own vtable pointer field. For example, the
+\cw{sesschan} type that handles the server side of an SSH terminal
+session will sometimes transform in mid-lifetime into an SCP or SFTP
+file-transfer channel in this way, at the point where the client sends
+an \cq{exec} or \cq{subsystem} request that indicates that that's what
+it wants to do with the channel.
+
+\lcont{
+
+This concept would be difficult to arrange in C++. In Rust, it
+wouldn't even \e{make sense}, because in Rust, objects implementing a
+trait don't even contain a vtable pointer at all \dash instead, the
+\q{trait object} type (identifying a specific instance of some
+implementation of a given trait) consists of a pair of pointers, one
+to the object itself and one to the vtable. In that model, the only
+way you could make an existing object turn into a different trait
+would be to know where all the pointers to it were stored elsewhere in
+the program, and persuade all their owners to rewrite them.
+
+}
+
+\b Another stunt you can do is to have a vtable that doesn't have a
+corresponding implementation structure at all, because the only
+methods implemented in it are the constructors, and they always end up
+returning an implementation of some other vtable. For example, some of
+PuTTY's crypto primitives have a hardware-accelerated version and a
+pure software version, and decide at run time which one to use (based
+on whether the CPU they're running on supports the necessary
+acceleration instructions). So, for example, there are vtables for
+\cw{ssh_sha256_sw} and \cw{ssh_sha256_hw}, each of which has its own
+data layout and its own implementations of all the methods; and then
+there's a top-level vtable \cw{ssh_sha256}, which only provides the
+\q{new} method, and implements it by calling the \q{new} method on one
+or other of the subtypes depending on what it finds out about the
+machine it's running on. That top-level selector vtable is nearly
+always the one used by client code. (Except for the test suite, which
+has to instantiate both of the subtypes in order to make sure they
+both pass the tests.)
+
+\lcont{
+
+As a result, the top-level selector vtable \cw{ssh_sha256} doesn't
+need to implement any method that takes an \cw{ssh_cipher *}
+parameter, because no \cw{ssh_cipher} object is ever constructed whose
+\cw{vt} field points to \cw{&ssh_sha256}: they all point to one of the
+other two full implementation vtables.
+
+}
+
 \H{udp-compile-once} Single compilation of each source file
 
 The PuTTY build system for any given platform works on the following

+ 108 - 35
source/putty/doc/using.but

@@ -520,6 +520,37 @@ connection made by another copy of PuTTY, you might find the
 which host key it should be expecting. See \k{config-loghost} for
 details of this.
 
+\H{using-serial} Connecting to a local serial line
+
+PuTTY can connect directly to a local serial line as an alternative
+to making a network connection. In this mode, text typed into the
+PuTTY window will be sent straight out of your computer's serial
+port, and data received through that port will be displayed in the
+PuTTY window. You might use this mode, for example, if your serial
+port is connected to another computer which has a serial connection.
+
+To make a connection of this type, simply select \q{Serial} from the
+\q{Connection type} radio buttons on the \q{Session} configuration
+panel (see \k{config-hostname}). The \q{Host Name} and \q{Port}
+boxes will transform into \q{Serial line} and \q{Speed}, allowing
+you to specify which serial line to use (if your computer has more
+than one) and what speed (baud rate) to use when transferring data.
+For further configuration options (data bits, stop bits, parity,
+flow control), you can use the \q{Serial} configuration panel (see
+\k{config-serial}).
+
+After you start up PuTTY in serial mode, you might find that you
+have to make the first move, by sending some data out of the serial
+line in order to notify the device at the other end that someone is
+there for it to talk to. This probably depends on the device. If you
+start up a PuTTY serial session and nothing appears in the window,
+try pressing Return a few times and see if that helps.
+
+A serial line provides no well defined means for one end of the
+connection to notify the other that the connection is finished.
+Therefore, PuTTY in serial mode will remain connected until you
+close the window using the close button.
+
 \H{using-rawprot} Making \i{raw TCP connections}
 
 A lot of \I{debugging Internet protocols}Internet protocols are
@@ -551,36 +582,61 @@ protocol}\q{Raw}, from the \q{Protocol} buttons in the \q{Session}
 configuration panel. (See \k{config-hostname}.) You can then enter a
 host name and a port number, and make the connection.
 
-\H{using-serial} Connecting to a local serial line
+\H{using-telnet} Connecting using the \i{Telnet} protocol
 
-PuTTY can connect directly to a local serial line as an alternative
-to making a network connection. In this mode, text typed into the
-PuTTY window will be sent straight out of your computer's serial
-port, and data received through that port will be displayed in the
-PuTTY window. You might use this mode, for example, if your serial
-port is connected to another computer which has a serial connection.
+PuTTY can use the Telnet protocol to connect to a server.
 
-To make a connection of this type, simply select \q{Serial} from the
-\q{Connection type} radio buttons on the \q{Session} configuration
-panel (see \k{config-hostname}). The \q{Host Name} and \q{Port}
-boxes will transform into \q{Serial line} and \q{Speed}, allowing
-you to specify which serial line to use (if your computer has more
-than one) and what speed (baud rate) to use when transferring data.
-For further configuration options (data bits, stop bits, parity,
-flow control), you can use the \q{Serial} configuration panel (see
-\k{config-serial}).
+Telnet was perhaps the most popular remote login protocol before SSH
+was introduced. It was general enough to be used by multiple server
+operating systems (Unix and VMS in particular), and supported many
+optional protocol extensions providing extra support for particular
+server features.
 
-After you start up PuTTY in serial mode, you might find that you
-have to make the first move, by sending some data out of the serial
-line in order to notify the device at the other end that someone is
-there for it to talk to. This probably depends on the device. If you
-start up a PuTTY serial session and nothing appears in the window,
-try pressing Return a few times and see if that helps.
+Unlike SSH, Telnet runs over an unsecured network connection, so it is
+a very bad idea to use it over the hostile Internet (though it is
+still used to some extent as of 2020).
 
-A serial line provides no well defined means for one end of the
-connection to notify the other that the connection is finished.
-Therefore, PuTTY in serial mode will remain connected until you
-close the window using the close button.
+\H{using-rlogin} Connecting using the \i{Rlogin} protocol
+
+PuTTY can use the Rlogin protocol to connect to a server.
+
+Rlogin was similar to Telnet in concept, but more focused on
+connections between Unix machines. It supported a feature for
+passwordless login, based on use of \q{privileged ports} (ports with
+numbers below 1024, which Unix traditionally does not allow users
+other than \cw{root} to allocate). Ultimately, based on the server
+trusting that the client's IP address was owned by the Unix machine it
+claimed to be, and that that machine would guard its privileged ports
+appropriately.
+
+Like Telnet, Rlogin runs over an unsecured network connection.
+
+\H{using-supdup} Connecting using the \i{SUPDUP} protocol
+
+PuTTY can use the SUPDUP protocol to connect to a server.
+
+SUPDUP is a login protocol used mainly by PDP-10 and Lisp machines
+during the period 1975-1990. Like Telnet and Rlogin, it is unsecured,
+so modern systems almost never support it.
+
+To make a connection of this type, select \q{SUPDUP} from the
+\q{Connection type} radio buttons on the \q{Session} panel (see
+\k{config-hostname}). For further configuration options (character
+set, more processing, scrolling), you can use the \q{SUPDUP}
+configuration panel (see \k{config-supdup}).
+
+In SUPDUP, terminal emulation is more integrated with the network
+protocol than in other protocols such as SSH. The SUPDUP protocol can
+thus only be used with PuTTY proper, not with the command-line tool
+Plink.
+
+The SUPDUP protocol does not support changing the terminal dimensions,
+so this capability is disabled during a SUPDUP session.
+
+SUPDUP provides no well defined means for one end of the connection to
+notify the other that the connection is finished.  Therefore, PuTTY in
+SUPDUP mode will remain connected until you close the window using the
+close button.
 
 \H{using-cmdline} The PuTTY command line
 
@@ -590,13 +646,13 @@ window}, or a \i{Windows shortcut}).
 
 \S{using-cmdline-session} Starting a session from the command line
 
-\I\c{-ssh}\I\c{-telnet}\I\c{-rlogin}\I\c{-raw}\I\c{-serial}These
+\I\c{-ssh}\I\c{-ssh-connection}\I\c{-telnet}\I\c{-rlogin}\I\c{-supdup}\I\c{-raw}\I\c{-serial}These
 options allow you to bypass the configuration window and launch
 straight into a session.
 
 To start a connection to a server called \c{host}:
 
-\c putty.exe [-ssh | -telnet | -rlogin | -raw] [user@]host
+\c putty.exe [-ssh | -ssh-connection | -telnet | -rlogin | -supdup | -raw] [user@]host
 
 If this syntax is used, settings are taken from the \i{Default Settings}
 (see \k{config-saving}); \c{user} overrides these settings if
@@ -662,23 +718,31 @@ must be the very first thing on the command line. This form of the
 option is deprecated.)
 
 \S2{using-cmdline-protocol} Selecting a protocol: \c{-ssh},
-\c{-telnet}, \c{-rlogin}, \c{-raw} \c{-serial}
+\c{-ssh-connection}, \c{-telnet}, \c{-rlogin}, \c{-supdup},
+\c{-raw}, \c{-serial}
 
 To choose which protocol you want to connect with, you can use one
 of these options:
 
 \b \i\c{-ssh} selects the SSH protocol.
 
+\b \i\c{-ssh-connection} selects the bare ssh-connection protocol.
+(This is only useful in specialised circumstances; see \k{config-psusan}
+for more information.)
+
 \b \i\c{-telnet} selects the Telnet protocol.
 
 \b \i\c{-rlogin} selects the Rlogin protocol.
 
+\b \i\c{-supdup} selects the SUPDUP protocol.
+
 \b \i\c{-raw} selects the raw protocol.
 
 \b \i\c{-serial} selects a serial connection.
 
-These options are not available in the file transfer tools PSCP and
-PSFTP (which only work with the SSH protocol).
+Most of these options are not available in the file transfer tools
+PSCP and PSFTP (which only work with the SSH protocol and the bare
+ssh-connection protocol).
 
 These options are equivalent to the \i{protocol selection} buttons
 in the Session panel of the PuTTY configuration box (see
@@ -782,8 +846,9 @@ security. If you possibly can, we recommend you set up public-key
 authentication instead. See \k{pubkey} for details.
 
 Note that the \c{-pw} option only works when you are using the SSH
-protocol. Due to fundamental limitations of Telnet and Rlogin, these
-protocols do not support automated password authentication.
+protocol. Due to fundamental limitations of Telnet, Rlogin, and
+SUPDUP, these protocols do not support automated password
+authentication.
 
 \S2{using-cmdline-agentauth} \i\c{-agent} and \i\c{-noagent}:
 control use of Pageant for authentication
@@ -1003,7 +1068,7 @@ For example, \cq{-sercfg 19200,8,n,1,N} denotes a baud rate of
 19200, 8 data bits, no parity, 1 stop bit and no flow control.
 
 \S2{using-cmdline-sshlog} \i\c{-sessionlog}, \i\c{-sshlog},
-\i\c{-sshrawlog}: specify session logging
+\i\c{-sshrawlog}: enable session logging
 
 These options cause the PuTTY network tools to write out a \i{log
 file}. Each of them expects a file name as an argument, e.g.
@@ -1019,6 +1084,14 @@ different logging modes, all available from the GUI too:
 
 For more information on logging configuration, see \k{config-logging}.
 
+\S2{using-cmdline-logfileexists} \i\c{-logoverwrite}, \i\c{-logappend}:
+control behaviour with existing log file
+
+If logging has been enabled (in the saved configuration, or by another
+command-line option), and the specified log file already exists, these
+options tell the PuTTY network tools what to do so that they don't
+have to ask the user. See \k{config-logfileexists} for details.
+
 \S2{using-cmdline-proxycmd} \i\c{-proxycmd}: specify a local proxy
 command
 
@@ -1060,7 +1133,7 @@ proxy command, you'll need to arrange to pass them the
 \c{-restrict-acl} option yourself, if that's what you want.)
 
 If Pageant is started with the \c{-restrict-acl} option, and you use
-it to launch a PuTTY session from its System Tray submenu, then
+it to launch a PuTTY session from its \ii{System Tray} submenu, then
 Pageant will \e{not} default to starting the PuTTY subprocess with a
 restricted ACL. This is because PuTTY is more likely to suffer reduced
 functionality as a result of restricted ACLs (e.g. screen reader

+ 8 - 3
source/putty/ecc.c

@@ -486,10 +486,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(
@@ -759,8 +759,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)
@@ -833,6 +833,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.
  */
@@ -1083,8 +1088,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];
@@ -1089,11 +1123,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;
@@ -1106,17 +1139,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;
@@ -1126,11 +1152,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;
@@ -1165,9 +1190,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;
@@ -1215,20 +1237,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;
+      }
     }
 
     /*
@@ -1286,13 +1307,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)
@@ -1305,9 +1325,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;
@@ -1705,11 +1725,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;
@@ -1722,16 +1741,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;
@@ -1742,11 +1755,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;
@@ -1771,12 +1783,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);
@@ -1789,7 +1800,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] == '"') {
@@ -1833,14 +1843,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);
@@ -1855,9 +1861,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;
@@ -1934,15 +1940,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];

+ 24 - 25
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 {
@@ -142,7 +142,7 @@ static void mainchan_open_confirmation(Channel *chan)
         struct X11FakeAuth *x11auth;
         bool retry_cmd_now = false;
 
-        if (conf_get_bool(mc->conf, CONF_x11_forward)) {;
+        if (conf_get_bool(mc->conf, CONF_x11_forward)) {
             char *x11_setup_err;
             if ((x11disp = x11_setup_display(
                      conf_get_str(mc->conf, CONF_x11_display),
@@ -236,7 +236,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
@@ -235,6 +248,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
@@ -344,8 +378,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,
@@ -364,6 +398,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);
 
 /*
@@ -402,4 +409,34 @@ static inline char *stripctrl_string(StripCtrlChars *sccpub, const char *str)
     return stripctrl_string_ptrlen(sccpub, ptrlen_from_asciz(str));
 }
 
+/*
+ * 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 - 109
source/putty/mpint.c

@@ -62,7 +62,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 */
@@ -119,6 +119,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
@@ -216,7 +224,7 @@ mp_int *mp_from_decimal_pl(ptrlen decimal)
 
     mp_int *x = mp_make_sized(words);
     for (size_t 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;
@@ -245,7 +253,7 @@ mp_int *mp_from_hex_pl(ptrlen hex)
     words = size_t_max(words, 1);
     mp_int *x = mp_make_sized(words);
     for (size_t 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));
@@ -905,7 +913,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;
@@ -926,13 +934,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.
@@ -1148,6 +1149,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;
@@ -1165,18 +1174,16 @@ 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 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 = 1 << bit;
+        size_t word_offset = (size_t)1 << bit;
         BignumInt mask = -(BignumInt)((wordshift >> bit) & 1);
         for (size_t i = 0; i < r->nw; i++) {
             BignumInt w = mp_word(r, i + word_offset);
@@ -1196,10 +1203,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;
@@ -1533,10 +1590,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
@@ -1548,9 +1605,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
@@ -1561,14 +1620,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
@@ -1586,11 +1645,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
@@ -1609,7 +1668,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));
 
@@ -1668,99 +1727,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.
      */
-    unsigned minus_one = b->w[0];
+    if (a_coeff_out || b_coeff_out) {
 
-    for (size_t 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);
@@ -1773,10 +1859,65 @@ static void mp_bezout_into(mp_int *a_coeff_out, mp_int *b_coeff_out,
 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)
 {
     /*
@@ -1901,7 +2042,7 @@ void mp_divmod_into(mp_int *n, mp_int *d, mp_int *q_out, mp_int *r_out)
      */
     size_t shift_up = 0;
     for (size_t 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? */
@@ -1938,7 +2079,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 (size_t 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? */
@@ -2118,6 +2259,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);
@@ -2400,10 +2617,8 @@ mp_int *mp_random_bits_fn(size_t bits, random_read_fn_t random_read)
     return toret;
 }
 
-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
@@ -2413,10 +2628,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

@@ -176,9 +176,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,
@@ -256,6 +257,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.
@@ -270,6 +282,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.
  *
@@ -360,10 +391,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);
 
 /*
@@ -376,6 +414,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);
 
 /*
@@ -391,13 +430,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);
@@ -264,9 +276,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.
@@ -283,7 +299,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);
 

+ 1 - 1
source/putty/noshare.c

@@ -15,7 +15,7 @@
 int platform_ssh_share(const char *name, Conf *conf,
                        Plug *downplug, Plug *upplug, Socket **sock,
                        char **logtext, char **ds_err, char **us_err,
-                       int can_upstream, int can_downstream)
+                       bool can_upstream, bool can_downstream)
 {
     return SHARE_NONE;
 }

+ 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);
         }
 
@@ -499,7 +498,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;
@@ -766,13 +765,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);
@@ -1084,19 +1082,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);
@@ -1317,41 +1314,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);
@@ -1456,7 +1452,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) \
@@ -1480,8 +1627,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);
@@ -1575,6 +1720,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
@@ -1620,6 +1767,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);
@@ -1633,6 +1781,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,
@@ -1682,6 +1837,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;
@@ -1695,6 +1855,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);
@@ -1726,10 +1899,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
  */
@@ -1759,6 +1928,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.
@@ -1789,9 +1964,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);
@@ -1890,6 +2070,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
  */
@@ -1948,14 +2131,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);
 
@@ -1975,8 +2180,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)
@@ -1253,6 +1265,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

@@ -311,8 +311,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;
     }
@@ -457,7 +457,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, ...)
 {
@@ -557,7 +558,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;
@@ -575,8 +576,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);
 
@@ -690,12 +691,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,
 };
 
 /*
@@ -704,7 +717,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)
 {
@@ -743,7 +756,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
@@ -765,7 +778,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 */
 
@@ -775,7 +788,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);
         }
     }
 
@@ -860,17 +873,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);
@@ -890,24 +914,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;
@@ -1053,21 +1081,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;
 }
 
@@ -1172,24 +1200,49 @@ 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,
 };

+ 193 - 66
source/putty/ssh.h

@@ -209,6 +209,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 */
@@ -292,11 +294,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. */
@@ -368,8 +369,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); }
 
@@ -385,7 +384,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);
 
@@ -399,6 +398,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);
@@ -475,6 +475,7 @@ struct ec_ecurve
     EdwardsCurve *ec;
     EdwardsPoint *G;
     mp_int *G_order;
+    unsigned log2_cofactor;
 };
 
 typedef enum EllipticCurveType {
@@ -504,6 +505,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);
@@ -527,6 +529,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
@@ -547,11 +567,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);
 
 uint32_t crc32_rfc1662(ptrlen data);
 uint32_t crc32_ssh1(ptrlen data);
@@ -717,32 +740,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 ")"
 
 void hash_simple(const ssh_hashalg *alg, ptrlen data, void *output);
 
@@ -780,6 +823,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);
@@ -816,6 +860,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)
@@ -885,8 +931,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;
@@ -931,20 +989,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;
@@ -957,6 +1029,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
@@ -966,6 +1042,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.
@@ -1126,13 +1203,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') ||
@@ -1147,21 +1217,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,
@@ -1205,24 +1329,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 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,
@@ -1234,8 +1381,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);
@@ -1244,30 +1393,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:
@@ -1354,6 +1479,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)                  \
@@ -1444,6 +1570,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.
@@ -1558,8 +1686,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

@@ -151,7 +151,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);
@@ -161,9 +161,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);
@@ -240,15 +262,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;
@@ -420,28 +441,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

@@ -35,16 +35,16 @@ static bool ssh1_connection_want_user_input(PacketProtocolLayer *ppl);
 static void ssh1_connection_got_user_input(PacketProtocolLayer *ppl);
 static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
 
-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 */
 };
 
 static void ssh1_rportfwd_remove(
@@ -62,34 +62,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(
@@ -100,28 +92,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);
@@ -790,14 +768,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

@@ -78,16 +78,16 @@ static bool ssh1_login_want_user_input(PacketProtocolLayer *ppl);
 static void ssh1_login_got_user_input(PacketProtocolLayer *ppl);
 static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
 
-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 */
 };
 
 static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req);
@@ -244,23 +244,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;
@@ -273,7 +274,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
                 return;
             }
         } else {
-            sfree(fingerprint);
+            ssh2_free_all_fingerprints(fingerprints);
             sfree(keystr);
         }
     }
@@ -438,7 +439,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
     pq_push(s->ppl.out_pq, pkt);
 
     ppl_logevent("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);
@@ -463,13 +464,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("Unable to load key file \"%s\" (%s)\r\n",
@@ -650,7 +651,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);
@@ -685,7 +686,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("Trying public key \"%s\"",
                          filename_to_str(s->keyfile));
@@ -699,7 +700,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 {
@@ -738,8 +739,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);
@@ -757,7 +757,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()");
                 }
             }
 
@@ -822,7 +822,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 */
@@ -855,7 +855,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;
@@ -1110,7 +1110,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(
@@ -481,5 +500,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

@@ -22,16 +22,16 @@ static bool ssh2_connection_want_user_input(PacketProtocolLayer *ppl);
 static void ssh2_connection_got_user_input(PacketProtocolLayer *ppl);
 static void ssh2_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
 
-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",
 };
 
 static SshChannel *ssh2_lportfwd_open(
@@ -62,34 +62,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)
@@ -132,28 +130,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);
@@ -516,19 +514,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:
@@ -1023,6 +1020,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;
 
     /*
      * Transfer data!
@@ -1251,6 +1249,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)) {
         /*
@@ -1695,14 +1702,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;
 
@@ -21,6 +19,7 @@ struct ssh2_connection_state {
 
     bool ssh_is_simple;
     bool persistent;
+    bool started;
 
     Conf *conf;
 
@@ -31,7 +30,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;
 
         /*
@@ -715,11 +720,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)) {
                 /*
@@ -773,25 +779,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 */
@@ -837,22 +843,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
@@ -864,8 +877,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.
@@ -876,11 +888,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 - 25
source/putty/ssh2transport.c

@@ -52,11 +52,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
@@ -77,16 +82,16 @@ static void ssh2_transport_set_max_data_size(struct ssh2_transport_state *s);
 static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def);
 static void ssh2_transport_higher_layer_packet_callback(void *context);
 
-static 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 */
 };
 
 #ifndef NO_GSSAPI
@@ -211,7 +216,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;
@@ -277,25 +281,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);
-            ssh_hash *h2 = ssh_hash_copy(h);
-            ssh_hash_final(h2, key + offset);
+            ssh_hash_digest_nondestructive(h, key + offset);
         }
 
-        ssh_hash_free(h);
     }
+
+    ssh_hash_free(h);
 }
 
 /*
@@ -373,6 +377,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;
         }
@@ -564,10 +626,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) {
         /*
@@ -757,6 +836,12 @@ static void ssh2_write_kexinit_lists(
                 add_to_commasep(list, kexlists[i][j].name);
             }
         }
+        if (i == KEXLIST_KEX && first_time) {
+            if (our_hostkeys)          /* we're the server */
+                add_to_commasep(list, "ext-info-s");
+            else                       /* we're the client */
+                add_to_commasep(list, "ext-info-c");
+        }
         put_stringsb(pktout, list);
     }
     /* List client->server languages. Empty list. */
@@ -772,7 +857,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;
@@ -933,6 +1019,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;
 
@@ -971,6 +1058,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
@@ -1141,7 +1242,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 */
 
         /*
@@ -1340,6 +1442,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)             \
     X(HK_DSA, ssh_dss)                          \
+    X(HK_RSA, ssh_rsa_sha512)                   \
+    X(HK_RSA, ssh_rsa_sha256)                   \
     X(HK_RSA, ssh_rsa)                          \
     /* end of list */
 #define COUNT_HOSTKEY_ALGORITHM(type, alg) +1
@@ -170,8 +174,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 */
@@ -180,6 +185,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

@@ -77,6 +77,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;
@@ -117,16 +119,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(
@@ -282,15 +284,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("Unable to load key file \"%s\" (%s)\r\n",
@@ -473,7 +473,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("Using username \"%s\".\r\n", s->username);
         }
         s->got_username = true;
@@ -526,7 +526,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:");
@@ -721,6 +721,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;
 
@@ -734,8 +747,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);
@@ -753,7 +765,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));
@@ -769,8 +781,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));
 
@@ -785,8 +796,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);
@@ -841,8 +852,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);
@@ -870,7 +898,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);
 
@@ -925,7 +953,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));
@@ -977,7 +1005,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);
@@ -995,8 +1023,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

@@ -85,7 +85,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.
  */
 
@@ -106,30 +106,54 @@ struct aes_extra {
 };
 
 #define VTABLES_INNER(cid, pid, bits, name, encsuffix,                  \
-                      decsuffix, setiv, flags)                          \
+                      decsuffix, setivsuffix, flagsval)                 \
     static void cid##_sw##encsuffix(ssh_cipher *, void *blk, int len);  \
     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)",                           \
+    };                                                                  \
                                                                         \
     static void cid##_hw##encsuffix(ssh_cipher *, void *blk, int len);  \
     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",          \
@@ -144,9 +168,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,30 +11,28 @@
 
 #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);
     ssh_cipher *cipher = ssh_cipher_new(&ssh_aes256_cbc);
     ssh_cipher_setkey(cipher, key);
     ssh_cipher_setiv(cipher, iv);
     return cipher;
 }
 
-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

@@ -290,29 +290,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)
@@ -356,109 +356,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.
  */
@@ -929,28 +826,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) {
@@ -1047,17 +944,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 - 2
source/putty/sshcrcda.c

@@ -44,8 +44,8 @@
 
 #define CMP(a, b)       (memcmp(a, b, SSH_BLOCKSIZE))
 
-uint8_t ONE[4] = { 1, 0, 0, 0 };
-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

@@ -430,7 +430,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,
@@ -683,16 +683,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[] = {
@@ -784,9 +802,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",
 };
 
 /* ----------------------------------------------------------------------
@@ -872,9 +899,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[] = {
@@ -998,7 +1034,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

@@ -85,6 +85,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 */
@@ -401,12 +418,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);
@@ -468,23 +485,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",
 };

+ 272 - 123
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)
  */
 
 /*
@@ -44,19 +44,20 @@
  */
 
 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);
 
@@ -68,7 +69,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;
@@ -78,11 +79,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);
@@ -213,6 +218,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 };
@@ -227,7 +261,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);
@@ -249,6 +284,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
  */
@@ -260,6 +332,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)
@@ -293,9 +369,9 @@ 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.
      */
-    for (size_t bit = 0; bit < 3; bit++)
+    for (unsigned bit = 0; bit < curve->e.log2_cofactor; bit++)
         mp_set_bit(e, bit, 0);
 
     return e;
@@ -334,16 +410,15 @@ 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);
     for (size_t i = 0; i < bytes; ++i)
         put_byte(bs, mp_get_byte(x, i));
 }
-#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)
@@ -462,20 +537,20 @@ 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);
 
     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. */
-    unsigned desired_x_parity = mp_get_bit(y, curve->fieldBits);
-    mp_set_bit(y, curve->fieldBits, 0);
-
     EdwardsPoint *P = ecc_edwards_point_new_from_y(
         curve->e.ec, y, desired_x_parity);
     mp_free(y);
@@ -640,6 +715,27 @@ static char *ecdsa_cache_str(ssh_key *key)
     return toret;
 }
 
+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);
@@ -652,6 +748,27 @@ static char *eddsa_cache_str(ssh_key *key)
     return toret;
 }
 
+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);
@@ -684,7 +801,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)
@@ -735,6 +852,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.
      */
     BinarySource subsrc[1];
     BinarySource_BARE_INIT_PL(subsrc, privkey_extended_pl);
@@ -772,7 +893,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);
     ptrlen priv = make_ptrlen(priv_sb->s + 4, priv_sb->len - 4);
 
     put_stringpl(bs, pub);
@@ -929,6 +1050,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);
@@ -1081,6 +1203,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);
@@ -1124,120 +1247,131 @@ static void eddsa_sign(ssh_key *key, ptrlen data,
     mp_free(s);
 }
 
-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,
 };
 
 /* ----------------------------------------------------------------------
@@ -1365,26 +1499,18 @@ 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;
-    }
     MontgomeryPoint *remote_p = ecc_montgomery_point_new(
         dh->curve->m.mc, remote_x);
     mp_free(remote_x);
 
     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);
 
@@ -1445,11 +1571,28 @@ static const struct eckex_extra kex_extra_curve25519 = {
     ssh_ecdhkex_m_getkey,
 };
 const ssh_kex ssh_ec_kex_curve25519 = {
+    "curve25519-sha256", NULL, KEXTYPE_ECDH,
+    &ssh_sha256, &kex_extra_curve25519,
+};
+/* Pre-RFC alias */
+const ssh_kex ssh_ec_kex_curve25519_libssh = {
     "[email protected]", NULL, KEXTYPE_ECDH,
     &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,
@@ -1461,7 +1604,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,
@@ -1473,7 +1616,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,
@@ -1486,7 +1629,9 @@ 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,
     &ssh_ec_kex_nistp384,
     &ssh_ec_kex_nistp521,
@@ -1532,6 +1677,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)
 {
@@ -1550,6 +1698,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
@@ -92,18 +90,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
@@ -112,11 +98,7 @@ static void hmac_key(ssh2_mac *mac, ptrlen key)
          */
         sb = strbuf_new_nm();
         strbuf_append(sb, ctx->hashalg->hlen);
-
-        ssh_hash *htmp = ssh_hash_new(ctx->hashalg);
-        put_datapl(htmp, key);
-        ssh_hash_final(htmp, sb->u);
-
+        hash_simple(ctx->hashalg, key, sb->u);
         kp = sb->u;
         klen = sb->len;
     } else {
@@ -127,18 +109,13 @@ 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);
     for (size_t i = 0; i < klen; i++)
         put_byte(ctx->h_outer, PAD_OUTER ^ kp[i]);
     for (size_t 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 (size_t i = 0; i < klen; i++)
         put_byte(ctx->h_inner, PAD_INNER ^ kp[i]);
     for (size_t i = klen; i < ctx->hashalg->blocklen; i++)
@@ -151,10 +128,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)
@@ -162,11 +136,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);
@@ -187,58 +160,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 - 220
source/putty/sshmd5.c

@@ -1,275 +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;
-        }
-        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));
     for (size_t i = 0; i < NCOLLECTORS; i++)
         pi->collectors[i] = ssh_hash_new(pi->hashalg);
     pi->until_reseed = 0;
@@ -138,8 +128,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));
     for (size_t i = 0; i < NCOLLECTORS; i++)
         ssh_hash_free(pi->collectors[i]);
     if (pi->generator)
@@ -184,6 +173,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);
 
@@ -192,7 +182,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;
 
     /*
@@ -201,44 +191,48 @@ 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);
 
     prngdebug("prng_read %"SIZEu"\n", size);
 
     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);
 }
@@ -269,18 +263,19 @@ void prng_add_entropy(prng *pr, unsigned source_id, ptrlen data)
         prng_reseed_time_ms() - pi->last_reseed_time >= 100) {
         prng_seed_begin(&pi->Prng);
 
+        unsigned char buf[MAX_HASH_LEN];
         uint32_t reseed_index = ++pi->reseeds;
         prngdebug("prng entropy reseed #%"PRIu32"\n", reseed_index);
         for (size_t 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);
     }
 }

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 448 - 298
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)
 {
@@ -97,10 +97,10 @@ void random_ref(void)
         random_create(&ssh_sha256);
 }
 
-void random_setup_special()
+void random_setup_custom(const ssh_hashalg *hash)
 {
     random_active++;
-    random_create(&ssh_sha512);
+    random_create(hash);
 }
 
 void random_reseed(ptrlen seed)

+ 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);
@@ -128,8 +143,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;
@@ -304,6 +319,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 >
@@ -374,6 +402,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)
 {
@@ -432,9 +469,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)
@@ -447,7 +488,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;
@@ -475,6 +516,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);
@@ -699,8 +746,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. */
@@ -721,7 +770,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);
@@ -750,6 +799,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;
@@ -771,7 +824,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;
@@ -786,26 +839,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)
@@ -832,16 +905,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++)
@@ -850,6 +924,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)
@@ -907,10 +983,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. */
@@ -953,7 +1026,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;
 
@@ -987,8 +1059,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

@@ -98,8 +98,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"),
 };
 
 /* ----------------------------------------------------------------------
@@ -276,26 +278,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)
@@ -315,19 +319,24 @@ 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);
 
     sha256_block_pad(&s->blk, BinarySink_UPCAST(s));
     for (size_t i = 0; i < 8; i++)
         PUT_32BIT_MSB_FIRST(digest + 4*i, s->core[i]);
-    sha256_sw_free(hash);
 }
 
 const ssh_hashalg ssh_sha256_sw = {
-    sha256_sw_new, sha256_sw_copy, sha256_sw_final, sha256_sw_free,
-    32, 64, HASHALG_NAMES_ANNOTATED("SHA-256", "unaccelerated"),
+    .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_ANNOTATED("SHA-256", "unaccelerated"),
 };
 
 /* ----------------------------------------------------------------------
@@ -602,13 +611,24 @@ static sha256_ni *sha256_ni_alloc(void)
     return s;
 }
 
-FUNC_ISA static ssh_hash *sha256_ni_new(const ssh_hashalg *alg)
+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);
@@ -616,26 +636,19 @@ FUNC_ISA 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;
 }
 
-static ssh_hash *sha256_ni_copy(ssh_hash *hash)
+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;
 }
 
 static void sha256_ni_free(ssh_hash *hash)
@@ -656,7 +669,7 @@ static void sha256_ni_write(BinarySink *bs, const void *vp, size_t len)
             sha256_ni_block(s->core, s->blk.block);
 }
 
-FUNC_ISA static void sha256_ni_final(ssh_hash *hash, uint8_t *digest)
+FUNC_ISA static void sha256_ni_digest(ssh_hash *hash, uint8_t *digest)
 {
     sha256_ni *s = container_of(hash, sha256_ni, hash);
 
@@ -677,13 +690,17 @@ FUNC_ISA 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);
 }
 
 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"),
 };
 
 /* ----------------------------------------------------------------------
@@ -818,28 +835,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)
@@ -858,19 +878,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"),
 };
 
 /* ----------------------------------------------------------------------
@@ -895,14 +920,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
+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 /* HW_SHA256 */

Vissa filer visades inte eftersom för många filer har ändrats