浏览代码

Merge branch 'thirdparty_dev' into dev

# Conflicts:
#	source/putty/crypto/aes-select.c
#	source/putty/crypto/aes-sw.c
#	source/putty/crypto/aes.h
#	source/putty/crypto/arcfour.c
#	source/putty/crypto/blowfish.c
#	source/putty/crypto/chacha20-poly1305.c
#	source/putty/crypto/des.c
#	source/putty/crypto/dsa.c
#	source/putty/crypto/ecc-ssh.c
#	source/putty/crypto/hmac.c
#	source/putty/crypto/rsa.c

#	source/putty/ssh.h
#	source/putty/ssh/common_p.c
#	source/putty/ssh/kex2-client.c
#	source/putty/ssh/ppl.h
#	source/putty/ssh/ssh.c
#	source/putty/ssh/transport2.c
#	source/putty/ssh/transport2.h
#	source/putty/ssh/userauth2-client.c
#	source/putty/sshpubk.c
#	source/putty/utils/tempseat.c
#	source/putty/windows/agent-client.c
#	source/putty/windows/gss.c
#	source/putty/windows/network.c
#	source/putty/windows/storage.c

Source commit: ca42122a58b67cfc547e8691e9f22c1ed8356cd6
Martin Prikryl 2 年之前
父节点
当前提交
53826431d2
共有 90 个文件被更改,包括 7400 次插入1640 次删除
  1. 6 0
      source/putty/crypto/aes-common.c
  2. 60 0
      source/putty/crypto/aes-ni.c
  3. 29 8
      source/putty/crypto/aes-select.c
  4. 94 1
      source/putty/crypto/aes-sw.c
  5. 56 5
      source/putty/crypto/aes.h
  6. 8 0
      source/putty/crypto/aesgcm-common.c
  7. 368 0
      source/putty/crypto/aesgcm-footer.h
  8. 364 0
      source/putty/crypto/aesgcm-ref-poly.c
  9. 38 0
      source/putty/crypto/aesgcm-select.c
  10. 145 0
      source/putty/crypto/aesgcm-sw.c
  11. 44 0
      source/putty/crypto/aesgcm.h
  12. 2 0
      source/putty/crypto/arcfour.c
  13. 9 9
      source/putty/crypto/bcrypt.c
  14. 14 10
      source/putty/crypto/blowfish.c
  15. 22 5
      source/putty/crypto/chacha20-poly1305.c
  16. 6 1
      source/putty/crypto/des.c
  17. 32 0
      source/putty/crypto/diffie-hellman.c
  18. 17 3
      source/putty/crypto/dsa.c
  19. 235 124
      source/putty/crypto/ecc-ssh.c
  20. 6 0
      source/putty/crypto/hmac.c
  21. 8 0
      source/putty/crypto/mpint.c
  22. 1160 0
      source/putty/crypto/openssh-certs.c
  23. 58 9
      source/putty/crypto/rsa.c
  24. 22 0
      source/putty/defs.h
  25. 49 51
      source/putty/import.c
  26. 1 1
      source/putty/marshal.h
  27. 56 5
      source/putty/misc.h
  28. 7 0
      source/putty/mpint.h
  29. 4 0
      source/putty/network.h
  30. 2 1
      source/putty/proxy/cproxy.c
  31. 11 9
      source/putty/proxy/proxy.c
  32. 24 24
      source/putty/proxy/telnet.c
  33. 137 23
      source/putty/putty.h
  34. 21 2
      source/putty/settings.c
  35. 254 31
      source/putty/ssh.h
  36. 15 9
      source/putty/ssh/bpp2.c
  37. 2 2
      source/putty/ssh/channel.h
  38. 159 11
      source/putty/ssh/common_p.c
  39. 3 3
      source/putty/ssh/connection2.c
  40. 1 1
      source/putty/ssh/gssc.c
  41. 216 77
      source/putty/ssh/kex2-client.c
  42. 1 1
      source/putty/ssh/mainchan.c
  43. 69 39
      source/putty/ssh/pgssapi.c
  44. 3 3
      source/putty/ssh/pgssapi.h
  45. 1 1
      source/putty/ssh/portfwd.c
  46. 5 4
      source/putty/ssh/ppl.h
  47. 40 43
      source/putty/ssh/sharing.c
  48. 7 4
      source/putty/ssh/ssh.c
  49. 338 92
      source/putty/ssh/transport2.c
  50. 32 16
      source/putty/ssh/transport2.h
  51. 557 216
      source/putty/ssh/userauth2-client.c
  52. 9 3
      source/putty/ssh/verstring.c
  53. 15 16
      source/putty/ssh/zlib.c
  54. 77 91
      source/putty/sshpubk.c
  55. 27 0
      source/putty/storage.h
  56. 11 0
      source/putty/stubs/null-cipher.c
  57. 22 0
      source/putty/stubs/null-key.c
  58. 0 0
      source/putty/stubs/null-lp.c
  59. 11 0
      source/putty/stubs/null-mac.c
  60. 20 0
      source/putty/stubs/null-opener.c
  61. 0 0
      source/putty/stubs/null-plug.c
  62. 12 1
      source/putty/stubs/null-seat.c
  63. 11 11
      source/putty/tree234.h
  64. 37 0
      source/putty/utils/base64_decode.c
  65. 40 0
      source/putty/utils/base64_encode.c
  66. 968 0
      source/putty/utils/cert-expr.c
  67. 2 2
      source/putty/utils/conf.c
  68. 22 0
      source/putty/utils/host_ca_new_free.c
  69. 93 0
      source/putty/utils/key_components.c
  70. 9 2
      source/putty/utils/log_proxy_stderr.c
  71. 41 0
      source/putty/utils/percent_decode.c
  72. 34 0
      source/putty/utils/percent_encode.c
  73. 16 0
      source/putty/utils/ptrlen.c
  74. 41 0
      source/putty/utils/seat_dialog_text.c
  75. 1 1
      source/putty/utils/smemeq.c
  76. 14 0
      source/putty/utils/strbuf.c
  77. 13 1
      source/putty/utils/tempseat.c
  78. 16 16
      source/putty/utils/tree234.c
  79. 4 4
      source/putty/version.h
  80. 2 2
      source/putty/windows/agent-client.c
  81. 4 7
      source/putty/windows/gss.c
  82. 7 0
      source/putty/windows/handle-socket.c
  83. 18 0
      source/putty/windows/local-proxy.c
  84. 289 252
      source/putty/windows/network.c
  85. 2 0
      source/putty/windows/no-jump-list.c
  86. 83 50
      source/putty/windows/platform.h
  87. 268 290
      source/putty/windows/storage.c
  88. 153 41
      source/putty/windows/unicode.c
  89. 184 0
      source/putty/windows/utils/registry.c
  90. 6 6
      source/putty/windows/utils/security_p.c

+ 6 - 0
source/putty/crypto/aes-common.c

@@ -12,3 +12,9 @@ const uint8_t aes_key_setup_round_constants[10] = {
      * regardless of the key. */
      * regardless of the key. */
     0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36,
     0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36,
 };
 };
+
+void aesgcm_cipher_crypt_length(
+    ssh_cipher *cipher, void *blk, int len, unsigned long seq)
+{
+    /* Do nothing: lengths are sent in clear for this cipher. */
+}

+ 60 - 0
source/putty/crypto/aes-ni.c

@@ -169,6 +169,16 @@ static inline __m128i aes_ni_sdctr_increment(__m128i v)
     return v;
     return v;
 }
 }
 
 
+/*
+ * Much simpler auxiliary routine to increment the counter for GCM
+ * mode. This only has to increment the low word.
+ */
+static inline __m128i aes_ni_gcm_increment(__m128i v)
+{
+    const __m128i ONE  = _mm_setr_epi32(1,0,0,0);
+    return _mm_add_epi32(v, ONE);
+}
+
 /*
 /*
  * Auxiliary routine to reverse the byte order of a vector, so that
  * Auxiliary routine to reverse the byte order of a vector, so that
  * the SDCTR IV can be made big-endian for feeding to the cipher.
  * the SDCTR IV can be made big-endian for feeding to the cipher.
@@ -247,6 +257,25 @@ struct aes_ni_context {
     ctx->iv = aes_ni_sdctr_reverse(counter);
     ctx->iv = aes_ni_sdctr_reverse(counter);
 }
 }
 
 
+static void aes_ni_setiv_gcm(ssh_cipher *ciph, const void *iv)
+{
+    aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+    __m128i counter = _mm_loadu_si128(iv);
+    ctx->iv = aes_ni_sdctr_reverse(counter);
+    ctx->iv = _mm_insert_epi32(ctx->iv, 1, 0);
+}
+
+static void aes_ni_next_message_gcm(ssh_cipher *ciph)
+{
+    aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+    uint32_t fixed = _mm_extract_epi32(ctx->iv, 3);
+    uint64_t msg_counter = _mm_extract_epi32(ctx->iv, 2);
+    msg_counter <<= 32;
+    msg_counter |= (uint32_t)_mm_extract_epi32(ctx->iv, 1);
+    msg_counter++;
+    ctx->iv = _mm_set_epi32(fixed, msg_counter >> 32, msg_counter, 1);
+}
+
 typedef __m128i (*aes_ni_fn)(__m128i v, const __m128i *keysched);
 typedef __m128i (*aes_ni_fn)(__m128i v, const __m128i *keysched);
 
 
 static inline void aes_cbc_ni_encrypt(
 static inline void aes_cbc_ni_encrypt(
@@ -295,6 +324,31 @@ static inline void aes_sdctr_ni(
     }
     }
 }
 }
 
 
+static inline void aes_encrypt_ecb_block_ni(
+    ssh_cipher *ciph, void *blk, aes_ni_fn encrypt)
+{
+    aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+    __m128i plaintext = _mm_loadu_si128(blk);
+    __m128i ciphertext = encrypt(plaintext, ctx->keysched_e);
+    _mm_storeu_si128(blk, ciphertext);
+}
+
+static inline void aes_gcm_ni(
+    ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn encrypt)
+{
+    aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+
+    for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen;
+         blk < finish; blk += 16) {
+        __m128i counter = aes_ni_sdctr_reverse(ctx->iv);
+        __m128i keystream = encrypt(counter, ctx->keysched_e);
+        __m128i input = _mm_loadu_si128((const __m128i *)blk);
+        __m128i output = _mm_xor_si128(input, keystream);
+        _mm_storeu_si128((__m128i *)blk, output);
+        ctx->iv = aes_ni_gcm_increment(ctx->iv);
+    }
+}
+
 #define NI_ENC_DEC(len)                                                 \
 #define NI_ENC_DEC(len)                                                 \
     /*static WINSCP*/ void aes##len##_ni_cbc_encrypt(                              \
     /*static WINSCP*/ void aes##len##_ni_cbc_encrypt(                              \
         ssh_cipher *ciph, void *vblk, int blklen)                       \
         ssh_cipher *ciph, void *vblk, int blklen)                       \
@@ -305,6 +359,12 @@ static inline void aes_sdctr_ni(
     /*static WINSCP*/ void aes##len##_ni_sdctr(                                    \
     /*static WINSCP*/ void aes##len##_ni_sdctr(                                    \
         ssh_cipher *ciph, void *vblk, int blklen)                       \
         ssh_cipher *ciph, void *vblk, int blklen)                       \
     { aes_sdctr_ni(ciph, vblk, blklen, aes_ni_##len##_e); }             \
     { aes_sdctr_ni(ciph, vblk, blklen, aes_ni_##len##_e); }             \
+    static void aes##len##_ni_gcm(                                      \
+        ssh_cipher *ciph, void *vblk, int blklen)                       \
+    { aes_gcm_ni(ciph, vblk, blklen, aes_ni_##len##_e); }               \
+    static void aes##len##_ni_encrypt_ecb_block(                        \
+        ssh_cipher *ciph, void *vblk)                                   \
+    { aes_encrypt_ecb_block_ni(ciph, vblk, aes_ni_##len##_e); }
 
 
 NI_ENC_DEC(128)
 NI_ENC_DEC(128)
 NI_ENC_DEC(192)
 NI_ENC_DEC(192)

+ 29 - 8
source/putty/crypto/aes-select.c

@@ -42,7 +42,7 @@ static ssh_cipher *aes_select(const ssh_cipheralg *alg)
 #define IF_NEON(...)
 #define IF_NEON(...)
 #endif
 #endif
 
 
-#define AES_SELECTOR_VTABLE(mode_c, mode_protocol, mode_display, bits)  \
+#define AES_SELECTOR_VTABLE(mode_c, id, mode_display, bits, ...)        \
     static const ssh_cipheralg *                                        \
     static const ssh_cipheralg *                                        \
     ssh_aes ## bits ## _ ## mode_c ## _impls[] = {                      \
     ssh_aes ## bits ## _ ## mode_c ## _impls[] = {                      \
         IF_NI(&ssh_aes ## bits ## _ ## mode_c ## _ni,)                  \
         IF_NI(&ssh_aes ## bits ## _ ## mode_c ## _ni,)                  \
@@ -54,7 +54,7 @@ static ssh_cipher *aes_select(const ssh_cipheralg *alg)
         /* WINSCP */ \
         /* WINSCP */ \
         /*.new =*/ aes_select,                                              \
         /*.new =*/ aes_select,                                              \
         NULL, NULL, NULL, NULL, NULL, NULL, NULL, \
         NULL, NULL, NULL, NULL, NULL, NULL, NULL, \
-        /*.ssh2_id =*/ "aes" #bits "-" mode_protocol,                       \
+        /*.ssh2_id =*/ id                                                   \
         /*.blksize =*/ 16,                                                  \
         /*.blksize =*/ 16,                                                  \
         /*.real_keybits =*/ bits,                                           \
         /*.real_keybits =*/ bits,                                           \
         /*.padded_keybytes =*/ bits/8,                                      \
         /*.padded_keybytes =*/ bits/8,                                      \
@@ -63,14 +63,26 @@ static ssh_cipher *aes_select(const ssh_cipheralg *alg)
         " (dummy selector vtable)",                                     \
         " (dummy selector vtable)",                                     \
         NULL, \
         NULL, \
         /*.extra =*/ ssh_aes ## bits ## _ ## mode_c ## _impls,              \
         /*.extra =*/ ssh_aes ## bits ## _ ## mode_c ## _impls,              \
+        __VA_ARGS__                                                         \
     }
     }
 
 
-AES_SELECTOR_VTABLE(cbc, "cbc", "CBC", 128);
-AES_SELECTOR_VTABLE(cbc, "cbc", "CBC", 192);
-AES_SELECTOR_VTABLE(cbc, "cbc", "CBC", 256);
-AES_SELECTOR_VTABLE(sdctr, "ctr", "SDCTR", 128);
-AES_SELECTOR_VTABLE(sdctr, "ctr", "SDCTR", 192);
-AES_SELECTOR_VTABLE(sdctr, "ctr", "SDCTR", 256);
+AES_SELECTOR_VTABLE(cbc, "aes128-cbc", "CBC", 128, );
+AES_SELECTOR_VTABLE(cbc, "aes192-cbc", "CBC", 192, );
+AES_SELECTOR_VTABLE(cbc, "aes256-cbc", "CBC", 256, );
+AES_SELECTOR_VTABLE(sdctr, "aes128-ctr", "SDCTR", 128, );
+AES_SELECTOR_VTABLE(sdctr, "aes192-ctr", "SDCTR", 192, );
+AES_SELECTOR_VTABLE(sdctr, "aes256-ctr", "SDCTR", 256, );
+AES_SELECTOR_VTABLE(gcm, "[email protected]", "GCM", 128,
+                    .required_mac = &ssh2_aesgcm_mac);
+AES_SELECTOR_VTABLE(gcm, "[email protected]", "GCM", 256,
+                    .required_mac = &ssh2_aesgcm_mac);
+
+/* 192-bit AES-GCM is included only so that testcrypt can run standard
+ * test vectors against it. OpenSSH doesn't define a protocol id for
+ * it. Hence setting its ssh2_id to NULL here, and more importantly,
+ * leaving it out of aesgcm_list[] below. */
+AES_SELECTOR_VTABLE(gcm, NULL, "GCM", 192,
+                    .required_mac = &ssh2_aesgcm_mac);
 
 
 static const ssh_cipheralg ssh_rijndael_lysator = {
 static const ssh_cipheralg ssh_rijndael_lysator = {
     /* Same as aes256_cbc, but with a different protocol ID */
     /* Same as aes256_cbc, but with a different protocol ID */
@@ -97,3 +109,12 @@ static const ssh_cipheralg *const aes_list[] = {
 };
 };
 
 
 const ssh2_ciphers ssh2_aes = { lenof(aes_list), aes_list };
 const ssh2_ciphers ssh2_aes = { lenof(aes_list), aes_list };
+
+static const ssh_cipheralg *const aesgcm_list[] = {
+    /* OpenSSH only defines protocol ids for 128- and 256-bit AES-GCM,
+     * not 192-bit. */
+    &ssh_aes128_gcm,
+    &ssh_aes256_gcm,
+};
+
+const ssh2_ciphers ssh2_aesgcm = { lenof(aesgcm_list), aesgcm_list };

+ 94 - 1
source/putty/crypto/aes-sw.c

@@ -838,6 +838,18 @@ struct aes_sw_context {
             uint8_t keystream[SLICE_PARALLELISM * 16];
             uint8_t keystream[SLICE_PARALLELISM * 16];
             uint8_t *keystream_pos;
             uint8_t *keystream_pos;
         } sdctr;
         } sdctr;
+        struct {
+            /* In GCM mode, the cipher preimage consists of three
+             * sections: one fixed, one that increments per message
+             * sent and MACed, and one that increments per cipher
+             * block. */
+            uint64_t msg_counter;
+            uint32_t fixed_iv, block_counter;
+            /* But we keep the precomputed keystream chunks just like
+             * SDCTR mode. */
+            uint8_t keystream[SLICE_PARALLELISM * 16];
+            uint8_t *keystream_pos;
+        } gcm;
     } iv;
     } iv;
     ssh_cipher ciph;
     ssh_cipher ciph;
 };
 };
@@ -888,6 +900,31 @@ static void aes_sw_setiv_sdctr(ssh_cipher *ciph, const void *viv)
         ctx->iv.sdctr.keystream + sizeof(ctx->iv.sdctr.keystream);
         ctx->iv.sdctr.keystream + sizeof(ctx->iv.sdctr.keystream);
 }
 }
 
 
+static void aes_sw_setiv_gcm(ssh_cipher *ciph, const void *viv)
+{
+    aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+    const uint8_t *iv = (const uint8_t *)viv;
+
+    ctx->iv.gcm.fixed_iv = GET_32BIT_MSB_FIRST(iv);
+    ctx->iv.gcm.msg_counter = GET_64BIT_MSB_FIRST(iv + 4);
+    ctx->iv.gcm.block_counter = 1;
+
+    /* Set keystream_pos to indicate that the keystream cache is
+     * currently empty */
+    ctx->iv.gcm.keystream_pos =
+        ctx->iv.gcm.keystream + sizeof(ctx->iv.gcm.keystream);
+}
+
+static void aes_sw_next_message_gcm(ssh_cipher *ciph)
+{
+    aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+
+    ctx->iv.gcm.msg_counter++;
+    ctx->iv.gcm.block_counter = 1;
+    ctx->iv.gcm.keystream_pos =
+        ctx->iv.gcm.keystream + sizeof(ctx->iv.gcm.keystream);
+}
+
 #endif
 #endif
 
 
 typedef void (*aes_sw_fn)(uint32_t v[4], const uint32_t *keysched);
 typedef void (*aes_sw_fn)(uint32_t v[4], const uint32_t *keysched);
@@ -1039,6 +1076,56 @@ static inline void aes_sdctr_sw(
     }
     }
 }
 }
 
 
+static inline void aes_encrypt_ecb_block_sw(ssh_cipher *ciph, void *blk)
+{
+    aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+    aes_sliced_e_serial(blk, blk, &ctx->sk);
+}
+
+static inline void aes_gcm_sw(
+    ssh_cipher *ciph, void *vblk, int blklen)
+{
+    aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+
+    /*
+     * GCM encrypt/decrypt looks just like SDCTR, except that the
+     * method of generating more keystream varies slightly.
+     */
+
+    uint8_t *keystream_end =
+        ctx->iv.gcm.keystream + sizeof(ctx->iv.gcm.keystream);
+
+    for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen;
+         blk < finish; blk += 16) {
+
+        if (ctx->iv.gcm.keystream_pos == keystream_end) {
+            /*
+             * Generate some keystream.
+             */
+            for (uint8_t *block = ctx->iv.gcm.keystream;
+                 block < keystream_end; block += 16) {
+                /* Format the counter value into the buffer. */
+                PUT_32BIT_MSB_FIRST(block, ctx->iv.gcm.fixed_iv);
+                PUT_64BIT_MSB_FIRST(block + 4, ctx->iv.gcm.msg_counter);
+                PUT_32BIT_MSB_FIRST(block + 12, ctx->iv.gcm.block_counter);
+
+                /* Increment the counter. */
+                ctx->iv.gcm.block_counter++;
+            }
+
+            /* Encrypt all those counter blocks. */
+            aes_sliced_e_parallel(ctx->iv.gcm.keystream,
+                                  ctx->iv.gcm.keystream, &ctx->sk);
+
+            /* Reset keystream_pos to the start of the buffer. */
+            ctx->iv.gcm.keystream_pos = ctx->iv.gcm.keystream;
+        }
+
+        memxor16(blk, blk, ctx->iv.gcm.keystream_pos);
+        ctx->iv.gcm.keystream_pos += 16;
+    }
+}
+
 #define SW_ENC_DEC(len)                                 \
 #define SW_ENC_DEC(len)                                 \
     /*WINSCP static*/ void aes##len##_sw_cbc_encrypt(              \
     /*WINSCP static*/ void aes##len##_sw_cbc_encrypt(              \
         ssh_cipher *ciph, void *vblk, int blklen)       \
         ssh_cipher *ciph, void *vblk, int blklen)       \
@@ -1048,7 +1135,13 @@ static inline void aes_sdctr_sw(
     { aes_cbc_sw_decrypt(ciph, vblk, blklen); }         \
     { aes_cbc_sw_decrypt(ciph, vblk, blklen); }         \
     /*WINSCP static*/ void aes##len##_sw_sdctr(                    \
     /*WINSCP static*/ void aes##len##_sw_sdctr(                    \
         ssh_cipher *ciph, void *vblk, int blklen)       \
         ssh_cipher *ciph, void *vblk, int blklen)       \
-    { aes_sdctr_sw(ciph, vblk, blklen); }
+    { aes_sdctr_sw(ciph, vblk, blklen); }               \
+    static void aes##len##_sw_gcm(                      \
+        ssh_cipher *ciph, void *vblk, int blklen)       \
+    { aes_gcm_sw(ciph, vblk, blklen); }                 \
+    static void aes##len##_sw_encrypt_ecb_block(        \
+        ssh_cipher *ciph, void *vblk)                   \
+    { aes_encrypt_ecb_block_sw(ciph, vblk); }
 
 
 #else // WINSCP_VS
 #else // WINSCP_VS
 
 

+ 56 - 5
source/putty/crypto/aes.h

@@ -15,6 +15,11 @@ struct aes_extra {
 
 
     /* Point to a writable substructure. */
     /* Point to a writable substructure. */
     struct aes_extra_mutable *mut;
     struct aes_extra_mutable *mut;
+
+    /* Extra API function specific to AES, to encrypt a single block
+     * in ECB mode without touching the IV. Used by AES-GCM MAC
+     * setup. */
+    void (*encrypt_ecb_block)(ssh_cipher *, void *);
 };
 };
 struct aes_extra_mutable {
 struct aes_extra_mutable {
     bool checked_availability;
     bool checked_availability;
@@ -30,6 +35,17 @@ static inline bool check_availability(const struct aes_extra *extra)
     return extra->mut->is_available;
     return extra->mut->is_available;
 }
 }
 
 
+/* Shared stub function for all the AES-GCM vtables. */
+void aesgcm_cipher_crypt_length(
+    ssh_cipher *cipher, void *blk, int len, unsigned long seq);
+
+/* External entry point for the encrypt_ecb_block function. */
+static inline void aes_encrypt_ecb_block(ssh_cipher *ciph, void *blk)
+{
+    const struct aes_extra *extra = ciph->vt->extra;
+    extra->encrypt_ecb_block(ciph, blk);
+}
+
 /*
 /*
  * Macros to define vtables for AES variants. There are a lot of
  * Macros to define vtables for AES variants. There are a lot of
  * these, because of the cross product between cipher modes, key
  * these, because of the cross product between cipher modes, key
@@ -37,14 +53,20 @@ static inline bool check_availability(const struct aes_extra *extra)
  * some effort here to reduce the boilerplate in the sub-files.
  * some effort here to reduce the boilerplate in the sub-files.
  */
  */
 
 
-#define AES_EXTRA(impl_c)                                               \
+#define AES_EXTRA_BITS(impl_c, bits)                                    \
     static struct aes_extra_mutable aes ## impl_c ## _extra_mut;        \
     static struct aes_extra_mutable aes ## impl_c ## _extra_mut;        \
-    static const struct aes_extra aes ## impl_c ## _extra = {           \
+    static const struct aes_extra aes ## bits ## impl_c ## _extra = {   \
         /* WINSCP */ \
         /* WINSCP */ \
         /*.check_available =*/ aes ## impl_c ## _available,                 \
         /*.check_available =*/ aes ## impl_c ## _available,                 \
         /*.mut =*/ &aes ## impl_c ## _extra_mut,                            \
         /*.mut =*/ &aes ## impl_c ## _extra_mut,                            \
+        .encrypt_ecb_block = &aes ## bits ## impl_c ## _encrypt_ecb_block, \
     }
     }
 
 
+#define AES_EXTRA(impl_c)                       \
+    AES_EXTRA_BITS(impl_c, 128);                \
+    AES_EXTRA_BITS(impl_c, 192);                \
+    AES_EXTRA_BITS(impl_c, 256)
+
 // WINSCP string constants are for avoiding 
 // WINSCP string constants are for avoiding 
 // Warning 1060: Different alignments specified for same segment, %s. Using highest alignment.rdata
 // Warning 1060: Different alignments specified for same segment, %s. Using highest alignment.rdata
 // in objconv
 // in objconv
@@ -60,6 +82,7 @@ static inline bool check_availability(const struct aes_extra *extra)
         /*.setkey =*/ aes ## impl_c ## _setkey,                             \
         /*.setkey =*/ aes ## impl_c ## _setkey,                             \
         /*.encrypt =*/ aes ## bits ## impl_c ## _cbc_encrypt,               \
         /*.encrypt =*/ aes ## bits ## impl_c ## _cbc_encrypt,               \
         /*.decrypt =*/ aes ## bits ## impl_c ## _cbc_decrypt,               \
         /*.decrypt =*/ aes ## bits ## impl_c ## _cbc_decrypt,               \
+        .next_message = nullcipher_next_message,                        \
         NULL, \
         NULL, \
         NULL, \
         NULL, \
         /*.ssh2_id =*/ ssh_aes ## bits ## _cbc ## impl_c ## ssh2_id, /*WINSCP*/ \
         /*.ssh2_id =*/ ssh_aes ## bits ## _cbc ## impl_c ## ssh2_id, /*WINSCP*/ \
@@ -69,7 +92,7 @@ static inline bool check_availability(const struct aes_extra *extra)
         /*.flags =*/ SSH_CIPHER_IS_CBC,                                     \
         /*.flags =*/ SSH_CIPHER_IS_CBC,                                     \
         /*.text_name =*/ ssh_aes ## bits ## _cbc ## impl_c ## text_name, /*WINSCP*/ \
         /*.text_name =*/ ssh_aes ## bits ## _cbc ## impl_c ## text_name, /*WINSCP*/ \
         NULL, \
         NULL, \
-        /*.extra =*/ &aes ## impl_c ## _extra,                              \
+        /*.extra =*/ &aes ## bits ## impl_c ## _extra,                              \
     }
     }
 
 
 #define AES_SDCTR_VTABLE(impl_c, impl_display, bits)                    \
 #define AES_SDCTR_VTABLE(impl_c, impl_display, bits)                    \
@@ -85,6 +108,7 @@ static inline bool check_availability(const struct aes_extra *extra)
         /*.decrypt =*/ aes ## bits ## impl_c ## _sdctr,                     \
         /*.decrypt =*/ aes ## bits ## impl_c ## _sdctr,                     \
         NULL, \
         NULL, \
         NULL, \
         NULL, \
+        .next_message = nullcipher_next_message,                        \
         /*.ssh2_id =*/ ssh_aes ## bits ## _sdctr ## impl_c ## ssh2_id, /*WINSCP*/ \
         /*.ssh2_id =*/ ssh_aes ## bits ## _sdctr ## impl_c ## ssh2_id, /*WINSCP*/ \
         /*.blksize =*/ 16,                                                  \
         /*.blksize =*/ 16,                                                  \
         /*.real_keybits =*/ bits,                                           \
         /*.real_keybits =*/ bits,                                           \
@@ -92,7 +116,31 @@ static inline bool check_availability(const struct aes_extra *extra)
         /*.flags =*/ 0,                                                     \
         /*.flags =*/ 0,                                                     \
         /*.text_name =*/ ssh_aes ## bits ## _sdctr ## impl_c ## text_name, /*WINSCP*/ \
         /*.text_name =*/ ssh_aes ## bits ## _sdctr ## impl_c ## text_name, /*WINSCP*/ \
         NULL, \
         NULL, \
-        /*.extra =*/ &aes ## impl_c ## _extra,                              \
+        /*.extra =*/ &aes ## bits ## impl_c ## _extra,                      \
+    }
+
+#define AES_GCM_VTABLE(impl_c, impl_display, bits)                      \
+    const ssh_cipheralg ssh_aes ## bits ## _gcm ## impl_c = {           \
+        .new = aes ## impl_c ## _new,                                   \
+        .free = aes ## impl_c ## _free,                                 \
+        .setiv = aes ## impl_c ## _setiv_gcm,                           \
+        .setkey = aes ## impl_c ## _setkey,                             \
+        .encrypt = aes ## bits ## impl_c ## _gcm,                       \
+        .decrypt = aes ## bits ## impl_c ## _gcm,                       \
+        .encrypt_length = aesgcm_cipher_crypt_length,                   \
+        .decrypt_length = aesgcm_cipher_crypt_length,                   \
+        .next_message = aes ## impl_c ## _next_message_gcm,             \
+        /* 192-bit AES-GCM is included only so that testcrypt can run   \
+         * standard test vectors against it. OpenSSH doesn't define a   \
+         * protocol id for it. So we set its ssh2_id to NULL. */        \
+        .ssh2_id = bits==192 ? NULL : "aes" #bits "[email protected]",   \
+        .blksize = 16,                                                  \
+        .real_keybits = bits,                                           \
+        .padded_keybytes = bits/8,                                      \
+        .flags = SSH_CIPHER_SEPARATE_LENGTH,                            \
+        .text_name = "AES-" #bits " GCM (" impl_display ")",            \
+        .required_mac = &ssh2_aesgcm_mac,                               \
+        .extra = &aes ## bits ## impl_c ## _extra,                      \
     }
     }
 
 
 #define AES_ALL_VTABLES(impl_c, impl_display)           \
 #define AES_ALL_VTABLES(impl_c, impl_display)           \
@@ -101,7 +149,10 @@ static inline bool check_availability(const struct aes_extra *extra)
     AES_CBC_VTABLE(impl_c, impl_display, 256);          \
     AES_CBC_VTABLE(impl_c, impl_display, 256);          \
     AES_SDCTR_VTABLE(impl_c, impl_display, 128);        \
     AES_SDCTR_VTABLE(impl_c, impl_display, 128);        \
     AES_SDCTR_VTABLE(impl_c, impl_display, 192);        \
     AES_SDCTR_VTABLE(impl_c, impl_display, 192);        \
-    AES_SDCTR_VTABLE(impl_c, impl_display, 256)
+    AES_SDCTR_VTABLE(impl_c, impl_display, 256);        \
+    AES_GCM_VTABLE(impl_c, impl_display, 128);          \
+    AES_GCM_VTABLE(impl_c, impl_display, 192);          \
+    AES_GCM_VTABLE(impl_c, impl_display, 256)
 
 
 /*
 /*
  * Macros to repeat a piece of code particular numbers of times that
  * Macros to repeat a piece of code particular numbers of times that

+ 8 - 0
source/putty/crypto/aesgcm-common.c

@@ -0,0 +1,8 @@
+#include "ssh.h"
+#include "aesgcm.h"
+
+void aesgcm_set_prefix_lengths(ssh2_mac *mac, size_t skip, size_t aad)
+{
+    const struct aesgcm_extra *extra = mac->vt->extra;
+    extra->set_prefix_lengths(mac, skip, aad);
+}

+ 368 - 0
source/putty/crypto/aesgcm-footer.h

@@ -0,0 +1,368 @@
+/*
+ * Common footer included by every implementation of the AES-GCM MAC.
+ *
+ * The difficult part of AES-GCM, which is done differently depending
+ * on what hardware acceleration is available, is the actual
+ * evaluation of a polynomial over GF(2^128) whose coefficients are
+ * 128-bit chunks of data. But preparing those chunks in the first
+ * place (out of the ciphertext, associated data, and an
+ * administrative block containing the lengths of both) is done in the
+ * same way no matter what technique is used for the evaluation, so
+ * that's centralised into this file, along with as much of the other
+ * functionality as posible.
+ *
+ * This footer file is #included by each implementation, but each one
+ * will define its own struct type for the state, so that each alloc
+ * function will test sizeof() a different structure, and similarly
+ * for free when it zeroes out the state on cleanup.
+ *
+ * The functions in the source file may be defined as 'inline' so that
+ * the functions in here can inline them. The 'coeff' function in
+ * particular probably should be, because that's called once per
+ * 16-byte block, so eliminating function call overheads is especially
+ * useful there.
+ *
+ * This footer has the following expectations from the source file
+ * that #includes it:
+ *
+ *  - define AESGCM_FLAVOUR to be a fragment of a C identifier that
+ *    will be included in all the function names (both the ones
+ *    defined in the implementation source file and those in here).
+ *    For example purposes below I'll suppose that this is 'foo'.
+ *
+ *  - define AESGCM_NAME to be a string literal that will be included
+ *    in the display name of the implementation.
+ *
+ *  - define a typedef 'aesgcm_foo' to be the state structure for the
+ *    implementation, and inside that structure, expand the macro
+ *    AESGCM_COMMON_FIELDS defined in aesgcm.h
+ *
+ *  - define the following functions:
+ *
+ *    // Determine whether this implementation is available at run time
+ *    static bool aesgcm_foo_available(void);
+ *
+ *    // Set up the 'key' of the polynomial part of the MAC, that is,
+ *    // the value at which the polynomial will be evaluated. 'var' is
+ *    // a 16-byte data block in the byte order it comes out of AES.
+ *    static void aesgcm_foo_setkey_impl(aesgcm_foo *ctx,
+ *                                       const unsigned char *var);
+ *
+ *    // Set up at the start of evaluating an individual polynomial.
+ *    // 'mask' is the 16-byte data block that will be XORed into the
+ *    // output value of the polynomial, also in AES byte order. This
+ *    // function should store 'mask' in whatever form is most
+ *    // convenient, and initialise an accumulator to zero.
+ *    static void aesgcm_foo_setup(aesgcm_foo *ctx,
+ *                                 const unsigned char *mask);
+ *
+ *    // Fold in a coefficient of the polynomial, by means of XORing
+ *    // it into the accumulator and then multiplying the accumulator
+ *    // by the variable passed to setkey_impl() above.
+ *    //
+ *    // 'coeff' points to the 16-byte block of data that the
+ *    // polynomial coefficient will be made out of.
+ *    //
+ *    // You probably want to mark this function 'inline'.
+ *    static void aesgcm_foo_coeff(aesgcm_foo *ctx,
+ *                                 const unsigned char *coeff);
+ *
+ *    // Generate the output MAC, by XORing the accumulator's final
+ *    // value with the mask passed to setup() above.
+ *    //
+ *    // 'output' points to a 16-byte region of memory to write the
+ *    // result to.
+ *    static void aesgcm_foo_output(aesgcm_foo *ctx,
+ *                                  unsigned char *output);
+ *
+ *  - if allocation of the state structure must be done in a
+ *    non-standard way (e.g. x86 needs this to force greater alignment
+ *    than standard malloc provides), then #define SPECIAL_ALLOC and
+ *    define this additional function:
+ *
+ *    // Allocate a state structure, zero out its contents, and return it.
+ *    static aesgcm_foo *aesgcm_foo_alloc(void);
+ *
+ *  - if freeing must also be done in an unusual way, #define
+ *    SPECIAL_FREE and define this function:
+ *
+ *    // Zero out the state structure to avoid information leaks if the
+ *    // memory is reused, and then free it.
+ *    static void aesgcm_foo_free(aesgcm_foo *ctx);
+ */
+
+#ifndef AESGCM_FLAVOUR
+#error AESGCM_FLAVOUR must be defined by any module including this footer
+#endif
+#ifndef AESGCM_NAME
+#error AESGCM_NAME must be defined by any module including this footer
+#endif
+
+#define CONTEXT CAT(aesgcm_, AESGCM_FLAVOUR)
+#define PREFIX(name) CAT(CAT(aesgcm_, AESGCM_FLAVOUR), CAT(_, name))
+
+#include "aes.h" // for aes_encrypt_ecb_block
+
+static const char *PREFIX(mac_text_name)(ssh2_mac *mac)
+{
+    return "AES-GCM (" AESGCM_NAME ")";
+}
+
+static void PREFIX(mac_next_message)(ssh2_mac *mac)
+{
+    CONTEXT *ctx = container_of(mac, CONTEXT, mac);
+
+    /*
+     * Make the mask value for a single MAC instance, by encrypting
+     * the all-zeroes word using the associated AES instance in its
+     * ordinary GCM fashion. This consumes the first block of
+     * keystream (with per-block counter equal to 1), leaving the
+     * second block of keystream ready to be used on the first block
+     * of plaintext.
+     */
+    unsigned char buf[16];
+    memset(buf, 0, 16);
+    ssh_cipher_encrypt(ctx->cipher, buf, 16);
+    PREFIX(setup)(ctx, buf); /* give it to the implementation to store */
+    smemclr(buf, sizeof(buf));
+}
+
+static void PREFIX(mac_setkey)(ssh2_mac *mac, ptrlen key)
+{
+    CONTEXT *ctx = container_of(mac, CONTEXT, mac);
+
+    /*
+     * Make the value of the polynomial variable, by encrypting the
+     * all-zeroes word using the associated AES instance in the
+     * special ECB mode. This is done via the special AES-specific API
+     * function encrypt_ecb_block, which doesn't touch the counter
+     * state at all.
+     */
+    unsigned char var[16];
+    memset(var, 0, 16);
+    aes_encrypt_ecb_block(ctx->cipher, var);
+    PREFIX(setkey_impl)(ctx, var);
+    smemclr(var, sizeof(var));
+
+    PREFIX(mac_next_message)(mac);     /* set up mask */
+}
+
+static void PREFIX(mac_start)(ssh2_mac *mac)
+{
+    CONTEXT *ctx = container_of(mac, CONTEXT, mac);
+
+    ctx->skipgot = ctx->aadgot = ctx->ciphertextlen = ctx->partlen = 0;
+}
+
+/*
+ * Handle receiving data via the BinarySink API and turning it into a
+ * collection of 16-byte blocks to use as polynomial coefficients.
+ *
+ * This code is written in a fully general way, which is able to
+ * handle an arbitrary number of bytes at the start of the data to
+ * ignore completely (necessary for PuTTY integration), and an
+ * arbitrary number to treat as associated data, and the rest will be
+ * regarded as ciphertext. The stream can be interrupted at any byte
+ * position and resumed later; a partial block will be stored as
+ * necessary.
+ *
+ * At the time of writing this comment, in live use most of that
+ * generality isn't required: the full data is passed to this function
+ * in just one call. But there's no guarantee of that staying true in
+ * future, so we do the full deal here just in case, and the test
+ * vectors in cryptsuite.py will test it. (And they'll use
+ * set_prefix_lengths to set up different configurations from the SSH
+ * usage.)
+ */
+static void PREFIX(mac_BinarySink_write)(
+    BinarySink *bs, const void *blkv, size_t len)
+{
+    CONTEXT *ctx = BinarySink_DOWNCAST(bs, CONTEXT);
+    const unsigned char *blk = (const unsigned char *)blkv;
+
+    /*
+     * Skip the prefix sequence number used as implicit extra data in
+     * SSH MACs. This is not included in the associated data field for
+     * GCM, because the IV incrementation policy provides its own
+     * sequence numbering.
+     */
+    if (ctx->skipgot < ctx->skiplen) {
+        size_t n = ctx->skiplen - ctx->skipgot;
+        if (n > len)
+            n = len;
+        blk += n;
+        len -= n;
+        ctx->skipgot += n;
+
+        if (len == 0)
+            return;
+    }
+
+    /*
+     * Read additional authenticated data and fold it in to the MAC.
+     */
+    while (ctx->aadgot < ctx->aadlen) {
+        size_t n = ctx->aadlen - ctx->aadgot;
+        if (n > len)
+            n = len;
+
+        if (ctx->partlen || n < 16) {
+            /*
+             * Fold data into the partial block.
+             */
+            if (n > 16 - ctx->partlen)
+                n = 16 - ctx->partlen;
+            memcpy(ctx->partblk + ctx->partlen, blk, n);
+            ctx->partlen += n;
+        } else if (n >= 16) {
+            /*
+             * Consume a whole block of AAD.
+             */
+            PREFIX(coeff)(ctx, blk);
+            n = 16;
+        }
+        blk += n;
+        len -= n;
+        ctx->aadgot += n;
+
+        if (ctx->partlen == 16) {
+            PREFIX(coeff)(ctx, ctx->partblk);
+            ctx->partlen = 0;
+        }
+
+        if (ctx->aadgot == ctx->aadlen && ctx->partlen) {
+            memset(ctx->partblk + ctx->partlen, 0, 16 - ctx->partlen);
+            PREFIX(coeff)(ctx, ctx->partblk);
+            ctx->partlen = 0;
+        }
+
+        if (len == 0)
+            return;
+    }
+
+    /*
+     * Read the main ciphertext and fold it in to the MAC.
+     */
+    while (len > 0) {
+        size_t n = len;
+
+        if (ctx->partlen || n < 16) {
+            /*
+             * Fold data into the partial block.
+             */
+            if (n > 16 - ctx->partlen)
+                n = 16 - ctx->partlen;
+            memcpy(ctx->partblk + ctx->partlen, blk, n);
+            ctx->partlen += n;
+        } else if (n >= 16) {
+            /*
+             * Consume a whole block of ciphertext.
+             */
+            PREFIX(coeff)(ctx, blk);
+            n = 16;
+        }
+        blk += n;
+        len -= n;
+        ctx->ciphertextlen += n;
+
+        if (ctx->partlen == 16) {
+            PREFIX(coeff)(ctx, ctx->partblk);
+            ctx->partlen = 0;
+        }
+    }
+}
+
+static void PREFIX(mac_genresult)(ssh2_mac *mac, unsigned char *output)
+{
+    CONTEXT *ctx = container_of(mac, CONTEXT, mac);
+
+    /*
+     * Consume any partial block of ciphertext remaining.
+     */
+    if (ctx->partlen) {
+        memset(ctx->partblk + ctx->partlen, 0, 16 - ctx->partlen);
+        PREFIX(coeff)(ctx, ctx->partblk);
+    }
+
+    /*
+     * Consume the final block giving the lengths of the AAD and ciphertext.
+     */
+    unsigned char blk[16];
+    memset(blk, 0, 16);
+    PUT_64BIT_MSB_FIRST(blk, ctx->aadlen * 8);
+    PUT_64BIT_MSB_FIRST(blk + 8, ctx->ciphertextlen * 8);
+    PREFIX(coeff)(ctx, blk);
+
+    /*
+     * And call the implementation's output function.
+     */
+    PREFIX(output)(ctx, output);
+
+    smemclr(blk, sizeof(blk));
+    smemclr(ctx->partblk, 16);
+}
+
+static ssh2_mac *PREFIX(mac_new)(const ssh2_macalg *alg, ssh_cipher *cipher)
+{
+    const struct aesgcm_extra *extra = alg->extra;
+    if (!check_aesgcm_availability(extra))
+        return NULL;
+
+#ifdef SPECIAL_ALLOC
+    CONTEXT *ctx = PREFIX(alloc)();
+#else
+    CONTEXT *ctx = snew(CONTEXT);
+    memset(ctx, 0, sizeof(CONTEXT));
+#endif
+
+    ctx->mac.vt = alg;
+    ctx->cipher = cipher;
+    /* Default values for SSH-2, overridable by set_prefix_lengths for
+     * testcrypt purposes */
+    ctx->skiplen = 4;
+    ctx->aadlen = 4;
+    BinarySink_INIT(ctx, PREFIX(mac_BinarySink_write));
+    BinarySink_DELEGATE_INIT(&ctx->mac, ctx);
+    return &ctx->mac;
+}
+
+static void PREFIX(set_prefix_lengths)(ssh2_mac *mac, size_t skip, size_t aad)
+{
+    CONTEXT *ctx = container_of(mac, CONTEXT, mac);
+    ctx->skiplen = skip;
+    ctx->aadlen = aad;
+}
+
+static void PREFIX(mac_free)(ssh2_mac *mac)
+{
+    CONTEXT *ctx = container_of(mac, CONTEXT, mac);
+#ifdef SPECIAL_FREE
+    PREFIX(free)(ctx);
+#else
+    smemclr(ctx, sizeof(*ctx));
+    sfree(ctx);
+#endif
+}
+
+static struct aesgcm_extra_mutable PREFIX(extra_mut);
+
+static const struct aesgcm_extra PREFIX(extra) = {
+    .check_available = PREFIX(available),
+    .mut = &PREFIX(extra_mut),
+    .set_prefix_lengths = PREFIX(set_prefix_lengths),
+};
+
+const ssh2_macalg CAT(ssh2_aesgcm_mac_, AESGCM_FLAVOUR) = {
+    .new = PREFIX(mac_new),
+    .free = PREFIX(mac_free),
+    .setkey = PREFIX(mac_setkey),
+    .start = PREFIX(mac_start),
+    .genresult = PREFIX(mac_genresult),
+    .next_message = PREFIX(mac_next_message),
+    .text_name = PREFIX(mac_text_name),
+    .name = "",
+    .etm_name = "", /* Not selectable independently */
+    .len = 16,
+    .keylen = 0,
+    .extra = &PREFIX(extra),
+};

+ 364 - 0
source/putty/crypto/aesgcm-ref-poly.c

@@ -0,0 +1,364 @@
+/*
+ * Implementation of the GCM polynomial hash in pure software, but
+ * based on a primitive that performs 64x64->128 bit polynomial
+ * multiplication over GF(2).
+ *
+ * This implementation is technically correct (should even be
+ * side-channel safe as far as I can see), but it's hopelessly slow,
+ * so no live SSH connection should ever use it. Therefore, it's
+ * deliberately not included in the lists in aesgcm-select.c. For pure
+ * software GCM in live use, you want aesgcm-sw.c, and that's what the
+ * selection system will choose.
+ *
+ * However, this implementation _is_ made available to testcrypt, so
+ * all the GCM tests in cryptsuite.py are run over this as well as the
+ * other implementations.
+ *
+ * The reason why this code exists at all is to act as a reference for
+ * GCM implementations that use a CPU-specific polynomial multiply
+ * intrinsic or asm statement. This version will run on whatever
+ * platform you're trying to port to, and will generate all the same
+ * intermediate results you expect the CPU-specific one to go through.
+ * So you can insert parallel diagnostics in this version and in your
+ * new version, to see where the two diverge.
+ *
+ * Also, this version is a good place to put long comments explaining
+ * the entire strategy of this implementation and its rationale. That
+ * avoids those comments having to be duplicated in the multiple
+ * platform-specific implementations, which can focus on commenting
+ * the way the local platform's idioms match up to this version, and
+ * refer to this file for the explanation of the underlying technique.
+ */
+
+#include "ssh.h"
+#include "aesgcm.h"
+
+/*
+ * Store a 128-bit value in the most convenient form standard C will
+ * let us, namely two uint64_t giving its most and least significant
+ * halves.
+ */
+typedef struct {
+    uint64_t hi, lo;
+} value128_t;
+
+typedef struct aesgcm_ref_poly {
+    AESGCM_COMMON_FIELDS;
+
+    /*
+     * The state of our GCM implementation is represented entirely by
+     * three 128-bit values:
+     */
+
+    /*
+     * The value at which we're evaluating the polynomial. The GCM
+     * spec calls this 'H'. It's defined once at GCM key setup time,
+     * by encrypting the all-zeroes value with our block cipher.
+     */
+    value128_t var;
+
+    /*
+     * Accumulator containing the result of evaluating the polynomial
+     * so far.
+     */
+    value128_t acc;
+
+    /*
+     * The mask value that is XORed into the final value of 'acc' to
+     * produce the output MAC. This is different for every MAC
+     * generated, because its purpose is to ensure that no information
+     * gathered from a legal MAC can be used to help the forgery of
+     * another one, and that comparing two legal MACs gives you no
+     * useful information about the text they cover, because in each
+     * case, the masks are different and pseudorandom.
+     */
+    value128_t mask;
+} aesgcm_ref_poly;
+
+static bool aesgcm_ref_poly_available(void)
+{
+    return true;  /* pure software implementation, always available */
+}
+
+/*
+ * Primitive function that takes two uint64_t values representing
+ * polynomials, multiplies them, and returns a value128_t struct
+ * containing the full product.
+ *
+ * Because the input polynomials have maximum degree 63, the output
+ * has max degree 63+63 = 127, not 128. As a result, the topmost bit
+ * of the output is always zero.
+ *
+ * The inside of this function is implemented in the simplest way,
+ * with no attention paid to performance. The important feature of
+ * this implementation is not what's _inside_ this function, but
+ * what's _outside_ it: aesgcm_ref_poly_coeff() tries to minimise the
+ * number of these operations.
+ */
+static value128_t pmul(uint64_t x, uint64_t y)
+{
+    value128_t r;
+    r.hi = r.lo = 0;
+
+    uint64_t bit = 1 & y;
+    r.lo ^= x & -bit;
+
+    for (unsigned i = 1; i < 64; i++) {
+        bit = 1 & (y >> i);
+        uint64_t z = x & -bit;
+        r.lo ^= z << i;
+        r.hi ^= z >> (64-i);
+    }
+
+    return r;
+}
+
+/*
+ * OK, I promised a long comment explaining what's going on in this
+ * implementation, and now it's time.
+ *
+ * The way AES-GCM _itself_ is defined by its own spec, its finite
+ * field consists of polynomials over GF(2), constrained to be 128
+ * bits long by reducing them modulo P = x^128 + x^7 + x^2 + x + 1.
+ * Using the usual binary representation in which bit i is the
+ * coefficient of x^i, that's 0x100000000000000000000000000000087.
+ *
+ * That is, whenever you multiply two polynomials and find a term
+ * x^128, you can replace it with x^7+x^2+x+1. More generally,
+ * x^(128+n) can be replaced with x^(7+n)+x^(2+n)+x^(1+n)+x^n. In
+ * binary terms, a 1 bit at the 128th position or above is replaced by
+ * 0x87 exactly 128 bits further down.
+ *
+ * So you'd think that multiplying two 128-bit polynomials mod P would
+ * be a matter of generating their full 256-bit product in the form of
+ * four words HI:HU:LU:LO, and then reducing it mod P by a two-stage
+ * process of computing HI * 0x87 and XORing it into HU:LU, then
+ * computing HU * 0x87 and XORing it into LU:LO.
+ *
+ * But it's not!
+ *
+ * The reason why not is because when AES-GCM is applied to SSH,
+ * somehow the _bit_ order got reversed. A 16-byte block of data in
+ * memory is converted into a polynomial by regarding bit 7 of the
+ * first byte as the constant term, bit 0 of the first byte as the x^7
+ * coefficient, ..., bit 0 of the last byte as the x^127 coefficient.
+ * So if we load that 16-byte block as a big-endian 128-bit integer,
+ * we end up with it representing a polynomial back to front, with the
+ * constant term at the top and the x^127 bit at the bottom.
+ *
+ * Well, that _shouldn't_ be a problem, right? The nice thing about
+ * polynomial multiplication is that it's essentially reversible. If
+ * you reverse the order of the coefficients of two polynomials, then
+ * the product of the reversed polys is exactly the reversal of the
+ * product of the original ones. So we bit-reverse our modulo
+ * polynomial to get 0x1c2000000000000000000000000000001, and we just
+ * pretend we're working mod that instead.
+ *
+ * And that is basically what we're about to do. But there's one
+ * complication, that arises from the semantics of the polynomial
+ * multiplication function we're using as our primitive operation.
+ *
+ * That function multiplies two polynomials of degree at most 63, to
+ * give one with degree at most 127. So it returns a 128-bit integer
+ * whose low bit is the constant term, and its very highest bit is 0,
+ * and its _next_ highest bit is the product of the high bits of the
+ * two inputs.
+ *
+ * That operation is _not_ symmetric in bit-reversal. If you give it
+ * the 64-bit-wise reversals of two polynomials P,Q, then its output
+ * is not the 128-bit-wise reversal of their product PQ, because that
+ * would require the constant term of PQ to appear in bit 127 of the
+ * output, and in fact it appears in bit 126. So in fact, what we get
+ * is offset by one bit from where we'd like it: it's the bit-reversal
+ * of PQx, not of PQ.
+ *
+ * There's more than one way we could fix this. One approach would be
+ * to work around this off-by-one error by taking the 128-bit output
+ * of pmul() and shifting it left by a bit. Then it _is_ the bitwise
+ * reversal of the 128-bit value we'd have wanted to get, and we could
+ * implement the exact algorithm described above, in fully
+ * bit-reversed form.
+ *
+ * But a 128-bit left shift is not a trivial operation in the vector
+ * architectures that this code is acting as a reference for. So we'd
+ * prefer to find a fix that doesn't need compensation during the
+ * actual per-block multiplication step.
+ *
+ * If we did the obvious thing anyway - compute the unshifted 128-bit
+ * product representing the bit-reversal of PQx, and reduce it mod
+ * 0x1c2000000000000000000000000000001 - then we'd get a result which
+ * is exactly what we want, except that it's got a factor of x in it
+ * that we need to get rid of. The obvious answer is to divide by x
+ * (which is legal and safe, since mod P, x is invertible).
+ *
+ * Dividing a 128-bit polynomial by x is easy in principle. Shift left
+ * (because we're still bit-reversed), and if that shifted a 1 bit off
+ * the top, XOR 0xc2000000000000000000000000000001 into the remaining
+ * 128 bits.
+ *
+ * But we're back to having that expensive left shift. What can we do
+ * about that?
+ *
+ * Happily, one of the two input values to our per-block multiply
+ * operation is fixed! It's given to us at key setup stage, and never
+ * changed until the next rekey. So if at key setup time we do this
+ * left shift business _once_, replacing the logical value Q with Q/x,
+ * then that exactly cancels out the unwanted factor of x that shows
+ * up in our multiply operation. And now it doesn't matter that it's
+ * expensive (in the sense of 'a few more instructions than you'd
+ * like'), because it only happens once per SSH key exchange, not once
+ * per 16 bytes of data transferred.
+ */
+
+static void aesgcm_ref_poly_setkey_impl(aesgcm_ref_poly *ctx,
+                                        const unsigned char *var)
+{
+    /*
+     * Key setup function. We copy the provided 16-byte 'var'
+     * value into our polynomial. But, as discussed above, we also
+     * need to divide it by x.
+     */
+
+    ctx->var.hi = GET_64BIT_MSB_FIRST(var);
+    ctx->var.lo = GET_64BIT_MSB_FIRST(var + 8);
+
+    uint64_t bit = 1 & (ctx->var.hi >> 63);
+    ctx->var.hi = (ctx->var.hi << 1) ^ (ctx->var.lo >> 63);
+    ctx->var.lo = (ctx->var.lo << 1) ^ bit;
+    ctx->var.hi ^= 0xC200000000000000 & -bit;
+}
+
+static inline void aesgcm_ref_poly_setup(aesgcm_ref_poly *ctx,
+                                         const unsigned char *mask)
+{
+    /*
+     * Set up to start evaluating a particular MAC. Copy in the mask
+     * value for this packet, and initialise acc to zero.
+     */
+
+    ctx->mask.hi = GET_64BIT_MSB_FIRST(mask);
+    ctx->mask.lo = GET_64BIT_MSB_FIRST(mask + 8);
+    ctx->acc.hi = ctx->acc.lo = 0;
+}
+
+static inline void aesgcm_ref_poly_coeff(aesgcm_ref_poly *ctx,
+                                         const unsigned char *coeff)
+{
+    /*
+     * One step of Horner's-rule polynomial evaluation (with each
+     * coefficient of the polynomial being an element of GF(2^128),
+     * itself composed of polynomials over GF(2) mod P).
+     *
+     * We take our accumulator value, add the incoming coefficient
+     * (which means XOR, by GF(2) rules), and multiply by x (that is,
+     * 'var').
+     */
+
+    /*
+     * The addition first, which is easy.
+     */
+    ctx->acc.hi ^= GET_64BIT_MSB_FIRST(coeff);
+    ctx->acc.lo ^= GET_64BIT_MSB_FIRST(coeff + 8);
+
+    /*
+     * First, create the 256-bit product of the two 128-bit
+     * polynomials over GF(2) stored in ctx->acc and ctx->var.
+     *
+     * The obvious way to do this is by four smaller multiplications
+     * of 64x64 -> 128 bits. But we can do better using a single
+     * iteration of the Karatsuba technique, which is actually more
+     * convenient in polynomials over GF(2) than it is in integers,
+     * because there aren't all those awkward carries complicating
+     * things.
+     *
+     * Letting B denote x^64, and imagining our two inputs are split
+     * up into 64-bit chunks ah,al,bh,bl, the product we want is
+     *
+     *         (ah B + al) (bh B + bl)
+     *       = (ah bh) B^2 + (al bh + ah bl) B + (al bl)
+     *
+     * which looks like four smaller multiplications of each of ah,al
+     * with each of bh,bl. But Karatsuba's trick is to first compute
+     *
+     *         (ah + al) (bh + bl)
+     *       = ah bh + al bh + ah bl + al bl
+     *
+     * and then subtract the terms (ah bh) and (al bl), which we had
+     * to compute anyway, to get the middle two terms (al bh + ah bl)
+     * which are our coefficient of B.
+     *
+     * This involves more bookkeeping instructions like XORs, but with
+     * any luck those are faster than the main multiplication.
+     */
+    uint64_t ah = ctx->acc.hi, al = ctx->acc.lo;
+    uint64_t bh = ctx->var.hi, bl = ctx->var.lo;
+    /* Compute the outer two terms */
+    value128_t lo = pmul(al, bl);
+    value128_t hi = pmul(ah, bh);
+    /* Compute the trick product (ah+al)(bh+bl) */
+    value128_t md = pmul(ah ^ al, bh ^ bl);
+    /* Subtract off the outer two terms to get md = al bh + ah bl */
+    md.hi ^= lo.hi ^ hi.hi;
+    md.lo ^= lo.lo ^ hi.lo;
+    /* And add that into the 256-bit value given by hi * x^128 + lo */
+    lo.hi ^= md.lo;
+    hi.lo ^= md.hi;
+
+    /*
+     * OK. Now hi and lo together make up the 256-bit full product.
+     * Now reduce it mod the reversal of the GCM modulus polynomial.
+     * As discussed above, that's 0x1c2000000000000000000000000000001.
+     *
+     * We want the _topmost_ 128 bits of this, because we're working
+     * in a bit-reversed world. So what we fundamentally want to do is
+     * to take our 256-bit product, and add to it the product of its
+     * low 128 bits with 0x1c2000000000000000000000000000001. Then the
+     * top 128 bits will be the output we want.
+     *
+     * Since there's no carrying in this arithmetic, it's enough to
+     * discard the 1 bit at the bottom of that, because it won't
+     * affect anything in the half we're keeping. So it's enough to
+     * add 0x1c2000000000000000000000000000000 * lo to (hi:lo).
+     *
+     * We can only work with 64 bits at a time, so the first thing we
+     * do is to break that up:
+     *
+     *  - add 0x1c200000000000000 * lo.lo to (hi.lo : lo.hi)
+     *  - add 0x1c200000000000000 * lo.hi to (hi.hi : hi.lo)
+     *
+     * But there's still a problem: 0x1c200000000000000 is just too
+     * _big_ to fit in 64 bits. So we have to break it up into the low
+     * 64 bits 0xc200000000000000, and its leading 1. So each of those
+     * steps of the form 'add 0x1c200000000000000 * x to y:z' becomes
+     * 'add 0xc200000000000000 * x to y:z' followed by 'add x to y',
+     * the latter step dealing with the leading 1.
+     */
+
+    /* First step, adding to the middle two words of our number. After
+     * this the lowest word (in lo.lo) is ignored. */
+    value128_t r1 = pmul(0xC200000000000000, lo.lo);
+    hi.lo ^= r1.hi ^ lo.lo;
+    lo.hi ^= r1.lo;
+
+    /* Second of those steps, adding to the top two words, and
+     * discarding lo.hi. */
+    value128_t r2 = pmul(0xC200000000000000, lo.hi);
+    hi.hi ^= r2.hi ^ lo.hi;
+    hi.lo ^= r2.lo;
+
+    /* Now 'hi' is precisely what we have left. */
+    ctx->acc = hi;
+}
+
+static inline void aesgcm_ref_poly_output(aesgcm_ref_poly *ctx,
+                                          unsigned char *output)
+{
+    PUT_64BIT_MSB_FIRST(output, ctx->acc.hi ^ ctx->mask.hi);
+    PUT_64BIT_MSB_FIRST(output + 8, ctx->acc.lo ^ ctx->mask.lo);
+    smemclr(&ctx->acc, 16);
+    smemclr(&ctx->mask, 16);
+}
+
+#define AESGCM_FLAVOUR ref_poly
+#define AESGCM_NAME "reference polynomial-based implementation"
+#include "aesgcm-footer.h"

+ 38 - 0
source/putty/crypto/aesgcm-select.c

@@ -0,0 +1,38 @@
+#include "ssh.h"
+#include "aesgcm.h"
+
+static ssh2_mac *aesgcm_mac_selector_new(const ssh2_macalg *alg,
+                                         ssh_cipher *cipher)
+{
+    static const ssh2_macalg *const real_algs[] = {
+#if HAVE_CLMUL
+        &ssh2_aesgcm_mac_clmul,
+#endif
+#if HAVE_NEON_PMULL
+        &ssh2_aesgcm_mac_neon,
+#endif
+        &ssh2_aesgcm_mac_sw,
+        NULL,
+    };
+
+    for (size_t i = 0; real_algs[i]; i++) {
+        const ssh2_macalg *alg = real_algs[i];
+        const struct aesgcm_extra *alg_extra =
+            (const struct aesgcm_extra *)alg->extra;
+        if (check_aesgcm_availability(alg_extra))
+            return ssh2_mac_new(alg, cipher);
+    }
+
+    /* We should never reach the NULL at the end of the list, because
+     * the last non-NULL entry should be software-only GCM, which is
+     * always available. */
+    unreachable("aesgcm_select ran off the end of its list");
+}
+
+const ssh2_macalg ssh2_aesgcm_mac = {
+    .new = aesgcm_mac_selector_new,
+    .name = "",
+    .etm_name = "", /* Not selectable independently */
+    .len = 16,
+    .keylen = 0,
+};

+ 145 - 0
source/putty/crypto/aesgcm-sw.c

@@ -0,0 +1,145 @@
+/*
+ * Implementation of the GCM polynomial hash in pure software.
+ *
+ * I don't know of a faster way to do this in a side-channel safe
+ * manner than by precomputing a giant table and iterating over the
+ * whole thing.
+ *
+ * The original GCM reference suggests that you precompute the effects
+ * of multiplying a 128-bit value by the fixed key, in the form of a
+ * table indexed by some number of bits of the input value, so that
+ * you end up computing something of the form
+ *
+ *   table1[x & 0xFF] ^ table2[(x>>8) & 0xFF] ^ ... ^ table15[(x>>120) & 0xFF]
+ *
+ * But that was obviously written before cache and timing leaks were
+ * known about. What's a time-safe approach?
+ *
+ * Well, the above technique isn't fixed to 8 bits of input per table.
+ * You could trade off the number of tables against the size of each
+ * table. At one extreme of this tradeoff, you have 128 tables each
+ * indexed by a single input bit - which is to say, you have 128
+ * values, each 128 bits wide, and you XOR together the subset of
+ * those values corresponding to the input bits, which you can do by
+ * making a bitmask out of each input bit using standard constant-
+ * time-coding bit twiddling techniques.
+ *
+ * That's pretty unpleasant when GCM is supposed to be a fast
+ * algorithm, but I don't know of a better approach that meets current
+ * security standards! Suggestions welcome, if they can get through
+ * testsc.
+ */
+
+#include "ssh.h"
+#include "aesgcm.h"
+
+/*
+ * Store a 128-bit value in the most convenient form standard C will
+ * let us, namely two uint64_t giving its most and least significant
+ * halves.
+ */
+typedef struct {
+    uint64_t hi, lo;
+} value128_t;
+
+typedef struct aesgcm_sw {
+    AESGCM_COMMON_FIELDS;
+
+    /* Accumulator for the current evaluation, and mask that will be
+     * XORed in at the end. High  */
+    value128_t acc, mask;
+
+    /*
+     * Table of values to XOR in for each bit, representing the effect
+     * of multiplying by the fixed key. The key itself doesn't need to
+     * be stored separately, because it's never used. (However, it is
+     * also the first entry in the table, so if you _do_ need it,
+     * there it is.)
+     *
+     * Table is indexed from the low bit of the input upwards.
+     */
+    value128_t table[128];
+} aesgcm_sw;
+
+static bool aesgcm_sw_available(void)
+{
+    return true;  /* pure software implementation, always available */
+}
+
+static void aesgcm_sw_setkey_impl(aesgcm_sw *gcm, const unsigned char *var)
+{
+    value128_t v;
+    v.hi = GET_64BIT_MSB_FIRST(var);
+    v.lo = GET_64BIT_MSB_FIRST(var + 8);
+
+    /*
+     * Prepare the table. This has to be done in reverse order, so
+     * that the original value of the variable corresponds to
+     * table[127], because AES-GCM works in the bit-reversal of its
+     * logical specification so that's where the logical constant term
+     * lives. (See more detailed comment in aesgcm-ref-poly.c.)
+     */
+    for (size_t i = 0; i < 128; i++) {
+        gcm->table[127 - i] = v;
+
+        /* Multiply v by x, which means shifting right (bit reversal
+         * again) and then adding 0xE1 at the top if we shifted a 1 out. */
+        uint64_t lobit = v.lo & 1;
+        v.lo = (v.lo >> 1) ^ (v.hi << 63);
+        v.hi = (v.hi >> 1) ^ (0xE100000000000000ULL & -lobit);
+    }
+}
+
+static inline void aesgcm_sw_setup(aesgcm_sw *gcm, const unsigned char *mask)
+{
+    gcm->mask.hi = GET_64BIT_MSB_FIRST(mask);
+    gcm->mask.lo = GET_64BIT_MSB_FIRST(mask + 8);
+    gcm->acc.hi = gcm->acc.lo = 0;
+}
+
+static inline void aesgcm_sw_coeff(aesgcm_sw *gcm, const unsigned char *coeff)
+{
+    /* XOR in the new coefficient */
+    gcm->acc.hi ^= GET_64BIT_MSB_FIRST(coeff);
+    gcm->acc.lo ^= GET_64BIT_MSB_FIRST(coeff + 8);
+
+    /* And now just loop over the bits of acc, making up a new value
+     * by XORing together the entries of 'table' corresponding to set
+     * bits. */
+
+    value128_t out;
+    out.lo = out.hi = 0;
+
+    const value128_t *tableptr = gcm->table;
+
+    for (size_t i = 0; i < 64; i++) {
+        uint64_t bit = 1 & gcm->acc.lo;
+        gcm->acc.lo >>= 1;
+        uint64_t mask = -bit;
+        out.hi ^= mask & tableptr->hi;
+        out.lo ^= mask & tableptr->lo;
+        tableptr++;
+    }
+    for (size_t i = 0; i < 64; i++) {
+        uint64_t bit = 1 & gcm->acc.hi;
+        gcm->acc.hi >>= 1;
+        uint64_t mask = -bit;
+        out.hi ^= mask & tableptr->hi;
+        out.lo ^= mask & tableptr->lo;
+        tableptr++;
+    }
+
+    gcm->acc = out;
+}
+
+static inline void aesgcm_sw_output(aesgcm_sw *gcm, unsigned char *output)
+{
+    PUT_64BIT_MSB_FIRST(output, gcm->acc.hi ^ gcm->mask.hi);
+    PUT_64BIT_MSB_FIRST(output + 8, gcm->acc.lo ^ gcm->mask.lo);
+    smemclr(&gcm->acc, 16);
+    smemclr(&gcm->mask, 16);
+}
+
+#define AESGCM_FLAVOUR sw
+#define AESGCM_NAME "unaccelerated"
+#include "aesgcm-footer.h"

+ 44 - 0
source/putty/crypto/aesgcm.h

@@ -0,0 +1,44 @@
+/*
+ * Common parts of the state structure for AESGCM MAC implementations.
+ */
+#define AESGCM_COMMON_FIELDS                    \
+    ssh_cipher *cipher;                         \
+    unsigned char partblk[16];                  \
+    size_t skiplen, aadlen, ciphertextlen;      \
+    size_t skipgot, aadgot, partlen;            \
+    BinarySink_IMPLEMENTATION;                  \
+    ssh2_mac mac
+
+/*
+ * The 'extra' structure is used to include information about how to
+ * check if a given implementation is available at run time, and
+ * whether we've already checked.
+ */
+struct aesgcm_extra_mutable;
+struct aesgcm_extra {
+    /* Function to check availability. Might be expensive, so we don't
+     * want to call it more than once. */
+    bool (*check_available)(void);
+
+    /* Point to a writable substructure. */
+    struct aesgcm_extra_mutable *mut;
+
+    /*
+     * Extra API function specific to this MAC type that allows
+     * testcrypt to set more general lengths for skiplen and aadlen.
+     */
+    void (*set_prefix_lengths)(ssh2_mac *mac, size_t skip, size_t aad);
+};
+struct aesgcm_extra_mutable {
+    bool checked_availability;
+    bool is_available;
+};
+static inline bool check_aesgcm_availability(const struct aesgcm_extra *extra)
+{
+    if (!extra->mut->checked_availability) {
+        extra->mut->is_available = extra->check_available();
+        extra->mut->checked_availability = true;
+    }
+
+    return extra->mut->is_available;
+}

+ 2 - 0
source/putty/crypto/arcfour.c

@@ -112,6 +112,7 @@ const ssh_cipheralg ssh_arcfour128_ssh2 = {
     /*.encrypt =*/ arcfour_ssh2_block,
     /*.encrypt =*/ arcfour_ssh2_block,
     /*.decrypt =*/ arcfour_ssh2_block,
     /*.decrypt =*/ arcfour_ssh2_block,
     NULL, NULL, // WINSCP
     NULL, NULL, // WINSCP
+    .next_message = nullcipher_next_message,
     /*.ssh2_id =*/ "arcfour128",
     /*.ssh2_id =*/ "arcfour128",
     /*.blksize =*/ 1,
     /*.blksize =*/ 1,
     /*.real_keybits =*/ 128,
     /*.real_keybits =*/ 128,
@@ -130,6 +131,7 @@ const ssh_cipheralg ssh_arcfour256_ssh2 = {
     /*.encrypt =*/ arcfour_ssh2_block,
     /*.encrypt =*/ arcfour_ssh2_block,
     /*.decrypt =*/ arcfour_ssh2_block,
     /*.decrypt =*/ arcfour_ssh2_block,
     NULL, NULL, // WINSCP
     NULL, NULL, // WINSCP
+    .next_message = nullcipher_next_message,
     /*.ssh2_id =*/ "arcfour256",
     /*.ssh2_id =*/ "arcfour256",
     /*.blksize =*/ 1,
     /*.blksize =*/ 1,
     /*.real_keybits =*/ 256,
     /*.real_keybits =*/ 256,

+ 9 - 9
source/putty/crypto/bcrypt.c

@@ -11,8 +11,8 @@
 #include "ssh.h"
 #include "ssh.h"
 #include "blowfish.h"
 #include "blowfish.h"
 
 
-BlowfishContext *bcrypt_setup(const unsigned char *key, int keybytes,
-                              const unsigned char *salt, int saltbytes)
+static BlowfishContext *bcrypt_setup(const unsigned char *key, int keybytes,
+                                     const unsigned char *salt, int saltbytes)
 {
 {
     int i;
     int i;
     BlowfishContext *ctx;
     BlowfishContext *ctx;
@@ -32,9 +32,9 @@ BlowfishContext *bcrypt_setup(const unsigned char *key, int keybytes,
     return ctx;
     return ctx;
 }
 }
 
 
-void bcrypt_hash(const unsigned char *key, int keybytes,
-                 const unsigned char *salt, int saltbytes,
-                 unsigned char output[32])
+static void bcrypt_hash(const unsigned char *key, int keybytes,
+                        const unsigned char *salt, int saltbytes,
+                        unsigned char output[32])
 {
 {
     BlowfishContext *ctx;
     BlowfishContext *ctx;
     int i;
     int i;
@@ -49,10 +49,10 @@ void bcrypt_hash(const unsigned char *key, int keybytes,
     blowfish_free_context(ctx);
     blowfish_free_context(ctx);
 }
 }
 
 
-void bcrypt_genblock(int counter,
-                     const unsigned char hashed_passphrase[64],
-                     const unsigned char *salt, int saltbytes,
-                     unsigned char output[32])
+static void bcrypt_genblock(int counter,
+                            const unsigned char hashed_passphrase[64],
+                            const unsigned char *salt, int saltbytes,
+                            unsigned char output[32])
 {
 {
     unsigned char hashed_salt[64];
     unsigned char hashed_salt[64];
 
 

+ 14 - 10
source/putty/crypto/blowfish.c

@@ -234,7 +234,7 @@ static const uint32_t sbox3[] = {
 #define ROUND(n) ( xL ^= P[n], t = xL, xL = F(xL) ^ xR, xR = t )
 #define ROUND(n) ( xL ^= P[n], t = xL, xL = F(xL) ^ xR, xR = t )
 
 
 static void blowfish_encrypt(uint32_t xL, uint32_t xR, uint32_t *output,
 static void blowfish_encrypt(uint32_t xL, uint32_t xR, uint32_t *output,
-                             BlowfishContext * ctx)
+                             BlowfishContext *ctx)
 {
 {
     uint32_t *S0 = ctx->S0;
     uint32_t *S0 = ctx->S0;
     uint32_t *S1 = ctx->S1;
     uint32_t *S1 = ctx->S1;
@@ -267,7 +267,7 @@ static void blowfish_encrypt(uint32_t xL, uint32_t xR, uint32_t *output,
 }
 }
 
 
 static void blowfish_decrypt(uint32_t xL, uint32_t xR, uint32_t *output,
 static void blowfish_decrypt(uint32_t xL, uint32_t xR, uint32_t *output,
-                             BlowfishContext * ctx)
+                             BlowfishContext *ctx)
 {
 {
     uint32_t *S0 = ctx->S0;
     uint32_t *S0 = ctx->S0;
     uint32_t *S1 = ctx->S1;
     uint32_t *S1 = ctx->S1;
@@ -300,7 +300,7 @@ static void blowfish_decrypt(uint32_t xL, uint32_t xR, uint32_t *output,
 }
 }
 
 
 static void blowfish_lsb_encrypt_cbc(unsigned char *blk, int len,
 static void blowfish_lsb_encrypt_cbc(unsigned char *blk, int len,
-                                     BlowfishContext * ctx)
+                                     BlowfishContext *ctx)
 {
 {
     uint32_t xL, xR, out[2], iv0, iv1;
     uint32_t xL, xR, out[2], iv0, iv1;
 
 
@@ -327,7 +327,7 @@ static void blowfish_lsb_encrypt_cbc(unsigned char *blk, int len,
     ctx->iv1 = iv1;
     ctx->iv1 = iv1;
 }
 }
 
 
-void blowfish_lsb_encrypt_ecb(void *vblk, int len, BlowfishContext * ctx)
+void blowfish_lsb_encrypt_ecb(void *vblk, int len, BlowfishContext *ctx)
 {
 {
     unsigned char *blk = (unsigned char *)vblk;
     unsigned char *blk = (unsigned char *)vblk;
     uint32_t xL, xR, out[2];
     uint32_t xL, xR, out[2];
@@ -346,7 +346,7 @@ void blowfish_lsb_encrypt_ecb(void *vblk, int len, BlowfishContext * ctx)
 }
 }
 
 
 static void blowfish_lsb_decrypt_cbc(unsigned char *blk, int len,
 static void blowfish_lsb_decrypt_cbc(unsigned char *blk, int len,
-                                     BlowfishContext * ctx)
+                                     BlowfishContext *ctx)
 {
 {
     uint32_t xL, xR, out[2], iv0, iv1;
     uint32_t xL, xR, out[2], iv0, iv1;
 
 
@@ -374,7 +374,7 @@ static void blowfish_lsb_decrypt_cbc(unsigned char *blk, int len,
 }
 }
 
 
 static void blowfish_msb_encrypt_cbc(unsigned char *blk, int len,
 static void blowfish_msb_encrypt_cbc(unsigned char *blk, int len,
-                                     BlowfishContext * ctx)
+                                     BlowfishContext *ctx)
 {
 {
     uint32_t xL, xR, out[2], iv0, iv1;
     uint32_t xL, xR, out[2], iv0, iv1;
 
 
@@ -402,7 +402,7 @@ static void blowfish_msb_encrypt_cbc(unsigned char *blk, int len,
 }
 }
 
 
 static void blowfish_msb_decrypt_cbc(unsigned char *blk, int len,
 static void blowfish_msb_decrypt_cbc(unsigned char *blk, int len,
-                                     BlowfishContext * ctx)
+                                     BlowfishContext *ctx)
 {
 {
     uint32_t xL, xR, out[2], iv0, iv1;
     uint32_t xL, xR, out[2], iv0, iv1;
 
 
@@ -430,7 +430,7 @@ static void blowfish_msb_decrypt_cbc(unsigned char *blk, int len,
 }
 }
 
 
 static void blowfish_msb_sdctr(unsigned char *blk, int len,
 static void blowfish_msb_sdctr(unsigned char *blk, int len,
-                                     BlowfishContext * ctx)
+                               BlowfishContext *ctx)
 {
 {
     uint32_t b[2], iv0, iv1, tmp;
     uint32_t b[2], iv0, iv1, tmp;
 
 
@@ -471,7 +471,7 @@ void blowfish_initkey(BlowfishContext *ctx)
     }
     }
 }
 }
 
 
-void blowfish_expandkey(BlowfishContext * ctx,
+void blowfish_expandkey(BlowfishContext *ctx,
                         const void *vkey, short keybytes,
                         const void *vkey, short keybytes,
                         const void *vsalt, short saltbytes)
                         const void *vsalt, short saltbytes)
 {
 {
@@ -655,7 +655,9 @@ const ssh_cipheralg ssh_blowfish_ssh1 = {
     /*.setkey =*/ blowfish_ssh_setkey,
     /*.setkey =*/ blowfish_ssh_setkey,
     /*.encrypt =*/ blowfish_ssh1_encrypt_blk,
     /*.encrypt =*/ blowfish_ssh1_encrypt_blk,
     /*.decrypt =*/ blowfish_ssh1_decrypt_blk,
     /*.decrypt =*/ blowfish_ssh1_decrypt_blk,
-    NULL, NULL, NULL, // WINSCP
+    NULL, NULL, // WINSCP
+    .next_message = nullcipher_next_message,
+    NULL, // WINSCP
     /*.blksize =*/ 8,
     /*.blksize =*/ 8,
     /*.real_keybits =*/ 128,
     /*.real_keybits =*/ 128,
     /*.padded_keybytes =*/ SSH1_SESSION_KEY_LENGTH,
     /*.padded_keybytes =*/ SSH1_SESSION_KEY_LENGTH,
@@ -673,6 +675,7 @@ const ssh_cipheralg ssh_blowfish_ssh2 = {
     /*.encrypt =*/ blowfish_ssh2_encrypt_blk,
     /*.encrypt =*/ blowfish_ssh2_encrypt_blk,
     /*.decrypt =*/ blowfish_ssh2_decrypt_blk,
     /*.decrypt =*/ blowfish_ssh2_decrypt_blk,
     NULL, NULL, // WINSCP
     NULL, NULL, // WINSCP
+    .next_message = nullcipher_next_message,
     /*.ssh2_id =*/ "blowfish-cbc",
     /*.ssh2_id =*/ "blowfish-cbc",
     /*.blksize =*/ 8,
     /*.blksize =*/ 8,
     /*.real_keybits =*/ 128,
     /*.real_keybits =*/ 128,
@@ -691,6 +694,7 @@ const ssh_cipheralg ssh_blowfish_ssh2_ctr = {
     /*.encrypt =*/ blowfish_ssh2_sdctr,
     /*.encrypt =*/ blowfish_ssh2_sdctr,
     /*.decrypt =*/ blowfish_ssh2_sdctr,
     /*.decrypt =*/ blowfish_ssh2_sdctr,
     NULL, NULL, // WINSCP
     NULL, NULL, // WINSCP
+    .next_message = nullcipher_next_message,
     /*.ssh2_id =*/ "blowfish-ctr",
     /*.ssh2_id =*/ "blowfish-ctr",
     /*.blksize =*/ 8,
     /*.blksize =*/ 8,
     /*.real_keybits =*/ 256,
     /*.real_keybits =*/ 256,

+ 22 - 5
source/putty/crypto/chacha20-poly1305.c

@@ -869,6 +869,7 @@ struct ccp_context {
     BinarySink_IMPLEMENTATION;
     BinarySink_IMPLEMENTATION;
     ssh_cipher ciph;
     ssh_cipher ciph;
     ssh2_mac mac_if;
     ssh2_mac mac_if;
+    bool ciph_allocated, mac_allocated;
 };
 };
 
 
 static ssh2_mac *poly_ssh2_new(
 static ssh2_mac *poly_ssh2_new(
@@ -876,13 +877,27 @@ static ssh2_mac *poly_ssh2_new(
 {
 {
     struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph);
     struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph);
     ctx->mac_if.vt = alg;
     ctx->mac_if.vt = alg;
+    ctx->mac_allocated = true;
     BinarySink_DELEGATE_INIT(&ctx->mac_if, ctx);
     BinarySink_DELEGATE_INIT(&ctx->mac_if, ctx);
     return &ctx->mac_if;
     return &ctx->mac_if;
 }
 }
 
 
+static void ccp_common_free(struct ccp_context *ctx)
+{
+    if (ctx->ciph_allocated || ctx->mac_allocated)
+        return;
+
+    smemclr(&ctx->a_cipher, sizeof(ctx->a_cipher));
+    smemclr(&ctx->b_cipher, sizeof(ctx->b_cipher));
+    smemclr(&ctx->mac, sizeof(ctx->mac));
+    sfree(ctx);
+}
+
 static void poly_ssh2_free(ssh2_mac *mac)
 static void poly_ssh2_free(ssh2_mac *mac)
 {
 {
-    /* Not allocated, just forwarded, no need to free */
+    struct ccp_context *ctx = container_of(mac, struct ccp_context, mac_if);
+    ctx->mac_allocated = false;
+    ccp_common_free(ctx);
 }
 }
 
 
 static void poly_setkey(ssh2_mac *mac, ptrlen key)
 static void poly_setkey(ssh2_mac *mac, ptrlen key)
@@ -950,6 +965,7 @@ const ssh2_macalg ssh2_poly1305 = {
     /*.setkey =*/ poly_setkey,
     /*.setkey =*/ poly_setkey,
     /*.start =*/ poly_start,
     /*.start =*/ poly_start,
     /*.genresult =*/ poly_genresult,
     /*.genresult =*/ poly_genresult,
+    .next_message = nullmac_next_message,
     /*.text_name =*/ poly_text_name,
     /*.text_name =*/ poly_text_name,
     /*.name =*/ "",
     /*.name =*/ "",
     /*.etm_name =*/ "", /* Not selectable individually, just part of
     /*.etm_name =*/ "", /* Not selectable individually, just part of
@@ -965,16 +981,16 @@ static ssh_cipher *ccp_new(const ssh_cipheralg *alg)
     BinarySink_INIT(ctx, poly_BinarySink_write);
     BinarySink_INIT(ctx, poly_BinarySink_write);
     poly1305_init(&ctx->mac);
     poly1305_init(&ctx->mac);
     ctx->ciph.vt = alg;
     ctx->ciph.vt = alg;
+    ctx->ciph_allocated = true;
+    ctx->mac_allocated = false;
     return &ctx->ciph;
     return &ctx->ciph;
 }
 }
 
 
 static void ccp_free(ssh_cipher *cipher)
 static void ccp_free(ssh_cipher *cipher)
 {
 {
     struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph);
     struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph);
-    smemclr(&ctx->a_cipher, sizeof(ctx->a_cipher));
-    smemclr(&ctx->b_cipher, sizeof(ctx->b_cipher));
-    smemclr(&ctx->mac, sizeof(ctx->mac));
-    sfree(ctx);
+    ctx->ciph_allocated = false;
+    ccp_common_free(ctx);
 }
 }
 
 
 static void ccp_iv(ssh_cipher *cipher, const void *iv)
 static void ccp_iv(ssh_cipher *cipher, const void *iv)
@@ -1049,6 +1065,7 @@ const ssh_cipheralg ssh2_chacha20_poly1305 = {
     /*.decrypt =*/ ccp_decrypt,
     /*.decrypt =*/ ccp_decrypt,
     /*.encrypt_length =*/ ccp_encrypt_length,
     /*.encrypt_length =*/ ccp_encrypt_length,
     /*.decrypt_length =*/ ccp_decrypt_length,
     /*.decrypt_length =*/ ccp_decrypt_length,
+    .next_message = nullcipher_next_message,
     /*.ssh2_id =*/ "[email protected]",
     /*.ssh2_id =*/ "[email protected]",
     /*.blksize =*/ 1,
     /*.blksize =*/ 1,
     /*.real_keybits =*/ 512,
     /*.real_keybits =*/ 512,

+ 6 - 1
source/putty/crypto/des.c

@@ -299,7 +299,7 @@ static inline uint32_t des_S(uint32_t si6420, uint32_t si7531)
         s73 ^= c73 & t->t73; c73 += 0x00080008;
         s73 ^= c73 & t->t73; c73 += 0x00080008;
     }
     }
     debug("S out: s40=%08"PRIx32" s62=%08"PRIx32
     debug("S out: s40=%08"PRIx32" s62=%08"PRIx32
-           " s51=%08"PRIx32" s73=%08"PRIx32"\n", s40, s62, s51, s73);
+          " s51=%08"PRIx32" s73=%08"PRIx32"\n", s40, s62, s51, s73);
 
 
     /* Final selection within each pair */
     /* Final selection within each pair */
     s40 ^= (s40 << 4) & ((0xf000/0x004) * (c40 & 0x00040004));
     s40 ^= (s40 << 4) & ((0xf000/0x004) * (c40 & 0x00040004));
@@ -699,6 +699,7 @@ const ssh_cipheralg ssh_des = {
     /*.encrypt =*/ des_cbc_encrypt,
     /*.encrypt =*/ des_cbc_encrypt,
     /*.decrypt =*/ des_cbc_decrypt,
     /*.decrypt =*/ des_cbc_decrypt,
     NULL, NULL, // WINSCP
     NULL, NULL, // WINSCP
+    .next_message = nullcipher_next_message,
     /*.ssh2_id =*/ "des-cbc",
     /*.ssh2_id =*/ "des-cbc",
     /*.blksize =*/ 8,
     /*.blksize =*/ 8,
     /*.real_keybits =*/ 56,
     /*.real_keybits =*/ 56,
@@ -718,6 +719,7 @@ const ssh_cipheralg ssh_des_sshcom_ssh2 = {
     /*.encrypt =*/ des_cbc_encrypt,
     /*.encrypt =*/ des_cbc_encrypt,
     /*.decrypt =*/ des_cbc_decrypt,
     /*.decrypt =*/ des_cbc_decrypt,
     NULL, NULL, // WINSCP
     NULL, NULL, // WINSCP
+    .next_message = nullcipher_next_message,
     /*.ssh2_id =*/ "[email protected]",
     /*.ssh2_id =*/ "[email protected]",
     /*.blksize =*/ 8,
     /*.blksize =*/ 8,
     /*.real_keybits =*/ 56,
     /*.real_keybits =*/ 56,
@@ -829,6 +831,7 @@ const ssh_cipheralg ssh_3des_ssh2 = {
     /*.encrypt =*/ des3_cbc1_cbc_encrypt,
     /*.encrypt =*/ des3_cbc1_cbc_encrypt,
     /*.decrypt =*/ des3_cbc1_cbc_decrypt,
     /*.decrypt =*/ des3_cbc1_cbc_decrypt,
     NULL, NULL, // WINSCP
     NULL, NULL, // WINSCP
+    .next_message = nullcipher_next_message,
     /*.ssh2_id =*/ "3des-cbc",
     /*.ssh2_id =*/ "3des-cbc",
     /*.blksize =*/ 8,
     /*.blksize =*/ 8,
     /*.real_keybits =*/ 168,
     /*.real_keybits =*/ 168,
@@ -938,6 +941,7 @@ const ssh_cipheralg ssh_3des_ssh2_ctr = {
     /*.encrypt =*/ des3_sdctr_encrypt_decrypt,
     /*.encrypt =*/ des3_sdctr_encrypt_decrypt,
     /*.decrypt =*/ des3_sdctr_encrypt_decrypt,
     /*.decrypt =*/ des3_sdctr_encrypt_decrypt,
     NULL, NULL, // WINSCP
     NULL, NULL, // WINSCP
+    .next_message = nullcipher_next_message,
     /*.ssh2_id =*/ "3des-ctr",
     /*.ssh2_id =*/ "3des-ctr",
     /*.blksize =*/ 8,
     /*.blksize =*/ 8,
     /*.real_keybits =*/ 168,
     /*.real_keybits =*/ 168,
@@ -1082,6 +1086,7 @@ const ssh_cipheralg ssh_3des_ssh1 = {
     /*.encrypt =*/ des3_cbc3_cbc_encrypt,
     /*.encrypt =*/ des3_cbc3_cbc_encrypt,
     /*.decrypt =*/ des3_cbc3_cbc_decrypt,
     /*.decrypt =*/ des3_cbc3_cbc_decrypt,
     NULL, NULL, // WINSCP
     NULL, NULL, // WINSCP
+    .next_message = nullcipher_next_message,
     NULL, // WINSCP
     NULL, // WINSCP
     /*.blksize =*/ 8,
     /*.blksize =*/ 8,
     /*.real_keybits =*/ 168,
     /*.real_keybits =*/ 168,

文件差异内容过多而无法显示
+ 32 - 0
source/putty/crypto/diffie-hellman.c


+ 17 - 3
source/putty/crypto/dsa.c

@@ -327,6 +327,12 @@ static void dsa_openssh_blob(ssh_key *key, BinarySink *bs)
     put_mp_ssh2(bs, dsa->x);
     put_mp_ssh2(bs, dsa->x);
 }
 }
 
 
+static bool dsa_has_private(ssh_key *key)
+{
+    struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
+    return dsa->x != NULL;
+}
+
 static int dsa_pubkey_bits(const ssh_keyalg *self, ptrlen pub)
 static int dsa_pubkey_bits(const ssh_keyalg *self, ptrlen pub)
 {
 {
     ssh_key *sshk;
     ssh_key *sshk;
@@ -345,8 +351,8 @@ static int dsa_pubkey_bits(const ssh_keyalg *self, ptrlen pub)
 }
 }
 
 
 mp_int *dsa_gen_k(const char *id_string, mp_int *modulus,
 mp_int *dsa_gen_k(const char *id_string, mp_int *modulus,
-                     mp_int *private_key,
-                     unsigned char *digest, int digest_len)
+                  mp_int *private_key,
+                  unsigned char *digest, int digest_len)
 {
 {
     /*
     /*
      * The basic DSA signing algorithm is:
      * The basic DSA signing algorithm is:
@@ -504,6 +510,8 @@ static void dsa_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs)
     } // WINSCP
     } // WINSCP
 }
 }
 
 
+static char *dsa_alg_desc(const ssh_keyalg *self) { return dupstr("DSA"); }
+
 const ssh_keyalg ssh_dsa = {
 const ssh_keyalg ssh_dsa = {
     // WINSCP
     // WINSCP
     /*.new_pub =*/ dsa_new_pub,
     /*.new_pub =*/ dsa_new_pub,
@@ -516,10 +524,16 @@ const ssh_keyalg ssh_dsa = {
     /*.public_blob =*/ dsa_public_blob,
     /*.public_blob =*/ dsa_public_blob,
     /*.private_blob =*/ dsa_private_blob,
     /*.private_blob =*/ dsa_private_blob,
     /*.openssh_blob =*/ dsa_openssh_blob,
     /*.openssh_blob =*/ dsa_openssh_blob,
+    .has_private = dsa_has_private,
     /*.cache_str =*/ dsa_cache_str,
     /*.cache_str =*/ dsa_cache_str,
     /*.components =*/ dsa_components,
     /*.components =*/ dsa_components,
+    .base_key = nullkey_base_key,
     /*.pubkey_bits =*/ dsa_pubkey_bits,
     /*.pubkey_bits =*/ dsa_pubkey_bits,
     /*.ssh_id =*/ "ssh-dss",
     /*.ssh_id =*/ "ssh-dss",
     /*.cache_id =*/ "dss",
     /*.cache_id =*/ "dss",
-    NULL, NULL,
+    NULL,
+    .supported_flags = nullkey_supported_flags,
+    .alternate_ssh_id = nullkey_alternate_ssh_id,
+    .alg_desc = dsa_alg_desc,
+    .variable_size = nullkey_variable_size_yes,
 };
 };

+ 235 - 124
source/putty/crypto/ecc-ssh.c

@@ -6,7 +6,7 @@
  * References:
  * References:
  *
  *
  * Elliptic curves in SSH are specified in RFC 5656:
  * Elliptic curves in SSH are specified in RFC 5656:
- *   http://tools.ietf.org/html/rfc5656
+ *   https://www.rfc-editor.org/rfc/rfc5656
  *
  *
  * That specification delegates details of public key formatting and a
  * That specification delegates details of public key formatting and a
  * lot of underlying mechanism to SEC 1:
  * lot of underlying mechanism to SEC 1:
@@ -402,6 +402,9 @@ struct ecsign_extra {
     const unsigned char *oid;
     const unsigned char *oid;
     int oidlen;
     int oidlen;
 
 
+    /* Human-readable algorithm description */
+    const char *alg_desc;
+
     /* Some EdDSA instances prefix a string to all hash preimages, to
     /* Some EdDSA instances prefix a string to all hash preimages, to
      * disambiguate which signature variant they're being used with */
      * disambiguate which signature variant they're being used with */
     ptrlen hash_prefix;
     ptrlen hash_prefix;
@@ -903,6 +906,12 @@ static void ecdsa_private_blob(ssh_key *key, BinarySink *bs)
     put_mp_ssh2(bs, ek->privateKey);
     put_mp_ssh2(bs, ek->privateKey);
 }
 }
 
 
+static bool ecdsa_has_private(ssh_key *key)
+{
+    struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
+    return ek->privateKey != NULL;
+}
+
 static void eddsa_private_blob(ssh_key *key, BinarySink *bs)
 static void eddsa_private_blob(ssh_key *key, BinarySink *bs)
 {
 {
     struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
     struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
@@ -912,6 +921,12 @@ static void eddsa_private_blob(ssh_key *key, BinarySink *bs)
     put_mp_le_fixedlen(bs, ek->privateKey, ek->curve->fieldBytes);
     put_mp_le_fixedlen(bs, ek->privateKey, ek->curve->fieldBytes);
 }
 }
 
 
+static bool eddsa_has_private(ssh_key *key)
+{
+    struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
+    return ek->privateKey != NULL;
+}
+
 static ssh_key *ecdsa_new_priv(const ssh_keyalg *alg, ptrlen pub, ptrlen priv)
 static ssh_key *ecdsa_new_priv(const ssh_keyalg *alg, ptrlen pub, ptrlen priv)
 {
 {
     ssh_key *sshk = ecdsa_new_pub(alg, pub);
     ssh_key *sshk = ecdsa_new_pub(alg, pub);
@@ -1441,9 +1456,16 @@ static void eddsa_sign(ssh_key *key, ptrlen data,
     } // WINSCP
     } // WINSCP
 }
 }
 
 
+static char *ec_alg_desc(const ssh_keyalg *self)
+{
+    const struct ecsign_extra *extra =
+        (const struct ecsign_extra *)self->extra;
+    return dupstr(extra->alg_desc);
+}
+
 static const struct ecsign_extra sign_extra_ed25519 = {
 static const struct ecsign_extra sign_extra_ed25519 = {
     ec_ed25519, &ssh_sha512,
     ec_ed25519, &ssh_sha512,
-    NULL, 0, PTRLEN_DECL_LITERAL(""),
+    NULL, 0, "Ed25519", PTRLEN_DECL_LITERAL(""),
 };
 };
 const ssh_keyalg ssh_ecdsa_ed25519 = {
 const ssh_keyalg ssh_ecdsa_ed25519 = {
     // WINSCP
     // WINSCP
@@ -1457,18 +1479,24 @@ const ssh_keyalg ssh_ecdsa_ed25519 = {
     /*.public_blob =*/ eddsa_public_blob,
     /*.public_blob =*/ eddsa_public_blob,
     /*.private_blob =*/ eddsa_private_blob,
     /*.private_blob =*/ eddsa_private_blob,
     /*.openssh_blob =*/ eddsa_openssh_blob,
     /*.openssh_blob =*/ eddsa_openssh_blob,
+    .has_private = eddsa_has_private,
     /*.cache_str =*/ eddsa_cache_str,
     /*.cache_str =*/ eddsa_cache_str,
     /*.components =*/ eddsa_components,
     /*.components =*/ eddsa_components,
     /*.pubkey_bits =*/ ec_shared_pubkey_bits,
     /*.pubkey_bits =*/ ec_shared_pubkey_bits,
+    .base_key = nullkey_base_key,
     /*.ssh_id =*/ "ssh-ed25519",
     /*.ssh_id =*/ "ssh-ed25519",
     /*.cache_id =*/ "ssh-ed25519",
     /*.cache_id =*/ "ssh-ed25519",
     /*.extra =*/ &sign_extra_ed25519,
     /*.extra =*/ &sign_extra_ed25519,
     0, // WINSCP
     0, // WINSCP
+    .supported_flags = nullkey_supported_flags,
+    .alternate_ssh_id = nullkey_alternate_ssh_id,
+    .alg_desc = ec_alg_desc,
+    .variable_size = nullkey_variable_size_no,
 };
 };
 
 
 static const struct ecsign_extra sign_extra_ed448 = {
 static const struct ecsign_extra sign_extra_ed448 = {
     ec_ed448, &ssh_shake256_114bytes,
     ec_ed448, &ssh_shake256_114bytes,
-    NULL, 0, PTRLEN_DECL_LITERAL("SigEd448\0\0"),
+    NULL, 0, "Ed448", PTRLEN_DECL_LITERAL("SigEd448\0\0"),
 };
 };
 const ssh_keyalg ssh_ecdsa_ed448 = {
 const ssh_keyalg ssh_ecdsa_ed448 = {
     // WINSCP
     // WINSCP
@@ -1482,13 +1510,19 @@ const ssh_keyalg ssh_ecdsa_ed448 = {
     /*.public_blob =*/ eddsa_public_blob,
     /*.public_blob =*/ eddsa_public_blob,
     /*.private_blob =*/ eddsa_private_blob,
     /*.private_blob =*/ eddsa_private_blob,
     /*.openssh_blob =*/ eddsa_openssh_blob,
     /*.openssh_blob =*/ eddsa_openssh_blob,
+    .has_private = eddsa_has_private,
     /*.cache_str =*/ eddsa_cache_str,
     /*.cache_str =*/ eddsa_cache_str,
     /*.components =*/ eddsa_components,
     /*.components =*/ eddsa_components,
     /*.pubkey_bits =*/ ec_shared_pubkey_bits,
     /*.pubkey_bits =*/ ec_shared_pubkey_bits,
+    .base_key = nullkey_base_key,
     /*.ssh_id =*/ "ssh-ed448",
     /*.ssh_id =*/ "ssh-ed448",
     /*.cache_id =*/ "ssh-ed448",
     /*.cache_id =*/ "ssh-ed448",
     /*.extra =*/ &sign_extra_ed448,
     /*.extra =*/ &sign_extra_ed448,
     0, // WINSCP
     0, // WINSCP
+    .supported_flags = nullkey_supported_flags,
+    .alternate_ssh_id = nullkey_alternate_ssh_id,
+    .alg_desc = ec_alg_desc,
+    .variable_size = nullkey_variable_size_no,
 };
 };
 
 
 /* OID: 1.2.840.10045.3.1.7 (ansiX9p256r1) */
 /* OID: 1.2.840.10045.3.1.7 (ansiX9p256r1) */
@@ -1497,7 +1531,7 @@ static const unsigned char nistp256_oid[] = {
 };
 };
 static const struct ecsign_extra sign_extra_nistp256 = {
 static const struct ecsign_extra sign_extra_nistp256 = {
     ec_p256, &ssh_sha256,
     ec_p256, &ssh_sha256,
-    nistp256_oid, lenof(nistp256_oid),
+    nistp256_oid, lenof(nistp256_oid), "NIST p256",
 };
 };
 const ssh_keyalg ssh_ecdsa_nistp256 = {
 const ssh_keyalg ssh_ecdsa_nistp256 = {
     // WINSCP
     // WINSCP
@@ -1511,13 +1545,19 @@ const ssh_keyalg ssh_ecdsa_nistp256 = {
     /*.public_blob =*/ ecdsa_public_blob,
     /*.public_blob =*/ ecdsa_public_blob,
     /*.private_blob =*/ ecdsa_private_blob,
     /*.private_blob =*/ ecdsa_private_blob,
     /*.openssh_blob =*/ ecdsa_openssh_blob,
     /*.openssh_blob =*/ ecdsa_openssh_blob,
+    .has_private = ecdsa_has_private,
     /*.cache_str =*/ ecdsa_cache_str,
     /*.cache_str =*/ ecdsa_cache_str,
     /*.components =*/ ecdsa_components,
     /*.components =*/ ecdsa_components,
     /*.pubkey_bits =*/ ec_shared_pubkey_bits,
     /*.pubkey_bits =*/ ec_shared_pubkey_bits,
+    .base_key = nullkey_base_key,
     /*.ssh_id =*/ "ecdsa-sha2-nistp256",
     /*.ssh_id =*/ "ecdsa-sha2-nistp256",
     /*.cache_id =*/ "ecdsa-sha2-nistp256",
     /*.cache_id =*/ "ecdsa-sha2-nistp256",
     /*.extra =*/ &sign_extra_nistp256,
     /*.extra =*/ &sign_extra_nistp256,
     0, // WINSCP
     0, // WINSCP
+    .supported_flags = nullkey_supported_flags,
+    .alternate_ssh_id = nullkey_alternate_ssh_id,
+    .alg_desc = ec_alg_desc,
+    .variable_size = nullkey_variable_size_no,
 };
 };
 
 
 /* OID: 1.3.132.0.34 (secp384r1) */
 /* OID: 1.3.132.0.34 (secp384r1) */
@@ -1526,7 +1566,7 @@ static const unsigned char nistp384_oid[] = {
 };
 };
 static const struct ecsign_extra sign_extra_nistp384 = {
 static const struct ecsign_extra sign_extra_nistp384 = {
     ec_p384, &ssh_sha384,
     ec_p384, &ssh_sha384,
-    nistp384_oid, lenof(nistp384_oid),
+    nistp384_oid, lenof(nistp384_oid), "NIST p384",
 };
 };
 const ssh_keyalg ssh_ecdsa_nistp384 = {
 const ssh_keyalg ssh_ecdsa_nistp384 = {
     // WINSCP
     // WINSCP
@@ -1540,13 +1580,19 @@ const ssh_keyalg ssh_ecdsa_nistp384 = {
     /*.public_blob =*/ ecdsa_public_blob,
     /*.public_blob =*/ ecdsa_public_blob,
     /*.private_blob =*/ ecdsa_private_blob,
     /*.private_blob =*/ ecdsa_private_blob,
     /*.openssh_blob =*/ ecdsa_openssh_blob,
     /*.openssh_blob =*/ ecdsa_openssh_blob,
+    .has_private = ecdsa_has_private,
     /*.cache_str =*/ ecdsa_cache_str,
     /*.cache_str =*/ ecdsa_cache_str,
     /*.components =*/ ecdsa_components,
     /*.components =*/ ecdsa_components,
     /*.pubkey_bits =*/ ec_shared_pubkey_bits,
     /*.pubkey_bits =*/ ec_shared_pubkey_bits,
+    .base_key = nullkey_base_key,
     /*.ssh_id =*/ "ecdsa-sha2-nistp384",
     /*.ssh_id =*/ "ecdsa-sha2-nistp384",
     /*.cache_id =*/ "ecdsa-sha2-nistp384",
     /*.cache_id =*/ "ecdsa-sha2-nistp384",
     /*.extra =*/ &sign_extra_nistp384,
     /*.extra =*/ &sign_extra_nistp384,
     0, // WINSCP
     0, // WINSCP
+    .supported_flags = nullkey_supported_flags,
+    .alternate_ssh_id = nullkey_alternate_ssh_id,
+    .alg_desc = ec_alg_desc,
+    .variable_size = nullkey_variable_size_no,
 };
 };
 
 
 /* OID: 1.3.132.0.35 (secp521r1) */
 /* OID: 1.3.132.0.35 (secp521r1) */
@@ -1555,7 +1601,7 @@ static const unsigned char nistp521_oid[] = {
 };
 };
 static const struct ecsign_extra sign_extra_nistp521 = {
 static const struct ecsign_extra sign_extra_nistp521 = {
     ec_p521, &ssh_sha512,
     ec_p521, &ssh_sha512,
-    nistp521_oid, lenof(nistp521_oid),
+    nistp521_oid, lenof(nistp521_oid), "NIST p521",
 };
 };
 const ssh_keyalg ssh_ecdsa_nistp521 = {
 const ssh_keyalg ssh_ecdsa_nistp521 = {
     // WINSCP
     // WINSCP
@@ -1569,137 +1615,153 @@ const ssh_keyalg ssh_ecdsa_nistp521 = {
     /*.public_blob =*/ ecdsa_public_blob,
     /*.public_blob =*/ ecdsa_public_blob,
     /*.private_blob =*/ ecdsa_private_blob,
     /*.private_blob =*/ ecdsa_private_blob,
     /*.openssh_blob =*/ ecdsa_openssh_blob,
     /*.openssh_blob =*/ ecdsa_openssh_blob,
+    .has_private = ecdsa_has_private,
     /*.cache_str =*/ ecdsa_cache_str,
     /*.cache_str =*/ ecdsa_cache_str,
     /*.components =*/ ecdsa_components,
     /*.components =*/ ecdsa_components,
     /*.pubkey_bits =*/ ec_shared_pubkey_bits,
     /*.pubkey_bits =*/ ec_shared_pubkey_bits,
+    .base_key = nullkey_base_key,
     /*.ssh_id =*/ "ecdsa-sha2-nistp521",
     /*.ssh_id =*/ "ecdsa-sha2-nistp521",
     /*.cache_id =*/ "ecdsa-sha2-nistp521",
     /*.cache_id =*/ "ecdsa-sha2-nistp521",
     /*.extra =*/ &sign_extra_nistp521,
     /*.extra =*/ &sign_extra_nistp521,
     0, // WINSCP
     0, // WINSCP
+    .supported_flags = nullkey_supported_flags,
+    .alternate_ssh_id = nullkey_alternate_ssh_id,
+    .alg_desc = ec_alg_desc,
+    .variable_size = nullkey_variable_size_no,
 };
 };
 
 
 /* ----------------------------------------------------------------------
 /* ----------------------------------------------------------------------
- * Exposed ECDH interface
+ * Exposed ECDH interfaces
  */
  */
 
 
 struct eckex_extra {
 struct eckex_extra {
     struct ec_curve *(*curve)(void);
     struct ec_curve *(*curve)(void);
-    void (*setup)(ecdh_key *dh);
-    void (*cleanup)(ecdh_key *dh);
-    void (*getpublic)(ecdh_key *dh, BinarySink *bs);
-    mp_int *(*getkey)(ecdh_key *dh, ptrlen remoteKey);
 };
 };
 
 
-struct ecdh_key {
+typedef struct ecdh_key_w {
     const struct eckex_extra *extra;
     const struct eckex_extra *extra;
     const struct ec_curve *curve;
     const struct ec_curve *curve;
     mp_int *private;
     mp_int *private;
-    union {
-        WeierstrassPoint *w_public;
-        MontgomeryPoint *m_public;
-    };
-};
+    WeierstrassPoint *w_public;
+
+    ecdh_key ek;
+} ecdh_key_w;
 
 
-const char *ssh_ecdhkex_curve_textname(const ssh_kex *kex)
+typedef struct ecdh_key_m {
+    const struct eckex_extra *extra;
+    const struct ec_curve *curve;
+    mp_int *private;
+    MontgomeryPoint *m_public;
+
+    ecdh_key ek;
+} ecdh_key_m;
+
+static ecdh_key *ssh_ecdhkex_w_new(const ssh_kex *kex, bool is_server)
 {
 {
     const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra;
     const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra;
-    struct ec_curve *curve = extra->curve();
-    return curve->textname;
-}
+    const struct ec_curve *curve = extra->curve();
+
+    ecdh_key_w *dhw = snew(ecdh_key_w);
+    dhw->ek.vt = kex->ecdh_vt;
+    dhw->extra = extra;
+    dhw->curve = curve;
 
 
-static void ssh_ecdhkex_w_setup(ecdh_key *dh)
-{
     mp_int *one = mp_from_integer(1);
     mp_int *one = mp_from_integer(1);
-    dh->private = mp_random_in_range(one, dh->curve->w.G_order);
+    dhw->private = mp_random_in_range(one, dhw->curve->w.G_order);
     mp_free(one);
     mp_free(one);
 
 
-    dh->w_public = ecc_weierstrass_multiply(dh->curve->w.G, dh->private);
+    dhw->w_public = ecc_weierstrass_multiply(dhw->curve->w.G, dhw->private);
+
+    return &dhw->ek;
 }
 }
 
 
-static void ssh_ecdhkex_m_setup(ecdh_key *dh)
+static ecdh_key *ssh_ecdhkex_m_new(const ssh_kex *kex, bool is_server)
 {
 {
+    const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra;
+    const struct ec_curve *curve = extra->curve();
+
+    ecdh_key_m *dhm = snew(ecdh_key_m);
+    dhm->ek.vt = kex->ecdh_vt;
+    dhm->extra = extra;
+    dhm->curve = curve;
+
     strbuf *bytes = strbuf_new_nm();
     strbuf *bytes = strbuf_new_nm();
-    random_read(strbuf_append(bytes, dh->curve->fieldBytes),
-                dh->curve->fieldBytes);
+    random_read(strbuf_append(bytes, dhm->curve->fieldBytes),
+                dhm->curve->fieldBytes);
 
 
-    dh->private = mp_from_bytes_le(ptrlen_from_strbuf(bytes));
+    dhm->private = mp_from_bytes_le(ptrlen_from_strbuf(bytes));
 
 
     /* Ensure the private key has the highest valid bit set, and no
     /* Ensure the private key has the highest valid bit set, and no
      * bits _above_ the highest valid one */
      * bits _above_ the highest valid one */
-    mp_reduce_mod_2to(dh->private, dh->curve->fieldBits);
-    mp_set_bit(dh->private, dh->curve->fieldBits - 1, 1);
+    mp_reduce_mod_2to(dhm->private, dhm->curve->fieldBits);
+    mp_set_bit(dhm->private, dhm->curve->fieldBits - 1, 1);
 
 
     /* Clear a curve-specific number of low bits */
     /* Clear a curve-specific number of low bits */
     { // WINSCP
     { // WINSCP
     unsigned bit;
     unsigned bit;
-    for (bit = 0; bit < dh->curve->m.log2_cofactor; bit++)
-        mp_set_bit(dh->private, bit, 0);
+    for (bit = 0; bit < dhm->curve->m.log2_cofactor; bit++)
+        mp_set_bit(dhm->private, bit, 0);
     } // WINSCP
     } // WINSCP
 
 
     strbuf_free(bytes);
     strbuf_free(bytes);
 
 
-    dh->m_public = ecc_montgomery_multiply(dh->curve->m.G, dh->private);
-}
-
-ecdh_key *ssh_ecdhkex_newkey(const ssh_kex *kex)
-{
-    const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra;
-    const struct ec_curve *curve = extra->curve();
+    dhm->m_public = ecc_montgomery_multiply(dhm->curve->m.G, dhm->private);
 
 
-    ecdh_key *dh = snew(ecdh_key);
-    dh->extra = extra;
-    dh->curve = curve;
-    dh->extra->setup(dh);
-    return dh;
+    return &dhm->ek;
 }
 }
 
 
 static void ssh_ecdhkex_w_getpublic(ecdh_key *dh, BinarySink *bs)
 static void ssh_ecdhkex_w_getpublic(ecdh_key *dh, BinarySink *bs)
 {
 {
-    put_wpoint(bs, dh->w_public, dh->curve, true);
+    ecdh_key_w *dhw = container_of(dh, ecdh_key_w, ek);
+    put_wpoint(bs, dhw->w_public, dhw->curve, true);
 }
 }
 
 
 static void ssh_ecdhkex_m_getpublic(ecdh_key *dh, BinarySink *bs)
 static void ssh_ecdhkex_m_getpublic(ecdh_key *dh, BinarySink *bs)
 {
 {
+    ecdh_key_m *dhm = container_of(dh, ecdh_key_m, ek);
     mp_int *x;
     mp_int *x;
     size_t i; // WINSCP
     size_t i; // WINSCP
-    ecc_montgomery_get_affine(dh->m_public, &x);
-    for (i = 0; i < dh->curve->fieldBytes; ++i)
+    ecc_montgomery_get_affine(dhm->m_public, &x);
+    for (i = 0; i < dhm->curve->fieldBytes; ++i)
         put_byte(bs, mp_get_byte(x, i));
         put_byte(bs, mp_get_byte(x, i));
     mp_free(x);
     mp_free(x);
 }
 }
 
 
-void ssh_ecdhkex_getpublic(ecdh_key *dh, BinarySink *bs)
+static bool ssh_ecdhkex_w_getkey(ecdh_key *dh, ptrlen remoteKey,
+                                 BinarySink *bs)
 {
 {
-    dh->extra->getpublic(dh, bs);
-}
+    ecdh_key_w *dhw = container_of(dh, ecdh_key_w, ek);
 
 
-static mp_int *ssh_ecdhkex_w_getkey(ecdh_key *dh, ptrlen remoteKey)
-{
-    WeierstrassPoint *remote_p = ecdsa_decode(remoteKey, dh->curve);
+    WeierstrassPoint *remote_p = ecdsa_decode(remoteKey, dhw->curve);
     if (!remote_p)
     if (!remote_p)
-        return NULL;
+        return false;
 
 
     if (ecc_weierstrass_is_identity(remote_p)) {
     if (ecc_weierstrass_is_identity(remote_p)) {
         /* Not a sensible Diffie-Hellman input value */
         /* Not a sensible Diffie-Hellman input value */
         ecc_weierstrass_point_free(remote_p);
         ecc_weierstrass_point_free(remote_p);
-        return NULL;
+        return false;
     }
     }
 
 
     { // WINSCP
     { // WINSCP
-    WeierstrassPoint *p = ecc_weierstrass_multiply(remote_p, dh->private);
+    WeierstrassPoint *p = ecc_weierstrass_multiply(remote_p, dhw->private);
 
 
     mp_int *x;
     mp_int *x;
     ecc_weierstrass_get_affine(p, &x, NULL);
     ecc_weierstrass_get_affine(p, &x, NULL);
+    put_mp_ssh2(bs, x);
+    mp_free(x);
 
 
     ecc_weierstrass_point_free(remote_p);
     ecc_weierstrass_point_free(remote_p);
     ecc_weierstrass_point_free(p);
     ecc_weierstrass_point_free(p);
 
 
-    return x;
+    return true;
     } // WINSCP
     } // WINSCP
 }
 }
 
 
-static mp_int *ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey)
+static bool ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey,
+                                 BinarySink *bs)
 {
 {
+    ecdh_key_m *dhm = container_of(dh, ecdh_key_m, ek);
+
     mp_int *remote_x = mp_from_bytes_le(remoteKey);
     mp_int *remote_x = mp_from_bytes_le(remoteKey);
 
 
     /* Per RFC 7748 section 5, discard any set bits of the other
     /* Per RFC 7748 section 5, discard any set bits of the other
@@ -1707,20 +1769,20 @@ static mp_int *ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey)
      * to represent all valid values. However, an overlarge value that
      * to represent all valid values. However, an overlarge value that
      * still fits into the remaining number of bits is accepted, and
      * still fits into the remaining number of bits is accepted, and
      * will be reduced mod p. */
      * will be reduced mod p. */
-    mp_reduce_mod_2to(remote_x, dh->curve->fieldBits);
+    mp_reduce_mod_2to(remote_x, dhm->curve->fieldBits);
 
 
     { // WINSCP
     { // WINSCP
     MontgomeryPoint *remote_p = ecc_montgomery_point_new(
     MontgomeryPoint *remote_p = ecc_montgomery_point_new(
-        dh->curve->m.mc, remote_x);
+        dhm->curve->m.mc, remote_x);
     mp_free(remote_x);
     mp_free(remote_x);
 
 
     { // WINSCP
     { // WINSCP
-    MontgomeryPoint *p = ecc_montgomery_multiply(remote_p, dh->private);
+    MontgomeryPoint *p = ecc_montgomery_multiply(remote_p, dhm->private);
 
 
     if (ecc_montgomery_is_identity(p)) {
     if (ecc_montgomery_is_identity(p)) {
         ecc_montgomery_point_free(remote_p);
         ecc_montgomery_point_free(remote_p);
         ecc_montgomery_point_free(p);
         ecc_montgomery_point_free(p);
-        return NULL;
+        return false;
     }
     }
 
 
     { // WINSCP
     { // WINSCP
@@ -1747,104 +1809,142 @@ static mp_int *ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey)
     { // WINSCP
     { // WINSCP
     strbuf *sb = strbuf_new();
     strbuf *sb = strbuf_new();
     size_t i;
     size_t i;
-    for (i = 0; i < dh->curve->fieldBytes; ++i)
+    for (i = 0; i < dhm->curve->fieldBytes; ++i)
         put_byte(sb, mp_get_byte(x, i));
         put_byte(sb, mp_get_byte(x, i));
     mp_free(x);
     mp_free(x);
     x = mp_from_bytes_be(ptrlen_from_strbuf(sb));
     x = mp_from_bytes_be(ptrlen_from_strbuf(sb));
     strbuf_free(sb);
     strbuf_free(sb);
+    put_mp_ssh2(bs, x);
+    mp_free(x);
 
 
-    return x;
+    return true;
     } // WINSCP
     } // WINSCP
     } // WINSCP
     } // WINSCP
     } // WINSCP
     } // WINSCP
     } // WINSCP
     } // WINSCP
 }
 }
 
 
-mp_int *ssh_ecdhkex_getkey(ecdh_key *dh, ptrlen remoteKey)
+static void ssh_ecdhkex_w_free(ecdh_key *dh)
 {
 {
-    return dh->extra->getkey(dh, remoteKey);
+    ecdh_key_w *dhw = container_of(dh, ecdh_key_w, ek);
+    mp_free(dhw->private);
+    ecc_weierstrass_point_free(dhw->w_public);
+    sfree(dhw);
 }
 }
 
 
-static void ssh_ecdhkex_w_cleanup(ecdh_key *dh)
+static void ssh_ecdhkex_m_free(ecdh_key *dh)
 {
 {
-    ecc_weierstrass_point_free(dh->w_public);
+    ecdh_key_m *dhm = container_of(dh, ecdh_key_m, ek);
+    mp_free(dhm->private);
+    ecc_montgomery_point_free(dhm->m_public);
+    sfree(dhm);
 }
 }
 
 
-static void ssh_ecdhkex_m_cleanup(ecdh_key *dh)
+static char *ssh_ecdhkex_description(const ssh_kex *kex)
 {
 {
-    ecc_montgomery_point_free(dh->m_public);
+    const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra;
+    const struct ec_curve *curve = extra->curve();
+    return dupprintf("ECDH key exchange with curve %s", curve->textname);
 }
 }
 
 
-void ssh_ecdhkex_freekey(ecdh_key *dh)
-{
-    mp_free(dh->private);
-    dh->extra->cleanup(dh);
-    sfree(dh);
-}
+static const struct eckex_extra kex_extra_curve25519 = { ec_curve25519 };
 
 
-static const struct eckex_extra kex_extra_curve25519 = {
-    ec_curve25519,
-    ssh_ecdhkex_m_setup,
-    ssh_ecdhkex_m_cleanup,
-    ssh_ecdhkex_m_getpublic,
-    ssh_ecdhkex_m_getkey,
+static const ecdh_keyalg ssh_ecdhkex_m_alg = {
+    .new = ssh_ecdhkex_m_new,
+    .free = ssh_ecdhkex_m_free,
+    .getpublic = ssh_ecdhkex_m_getpublic,
+    .getkey = ssh_ecdhkex_m_getkey,
+    .description = ssh_ecdhkex_description,
 };
 };
 const ssh_kex ssh_ec_kex_curve25519 = {
 const ssh_kex ssh_ec_kex_curve25519 = {
-    "curve25519-sha256", NULL, KEXTYPE_ECDH,
-    &ssh_sha256, &kex_extra_curve25519,
+    .name = "curve25519-sha256",
+    .main_type = KEXTYPE_ECDH,
+    .hash = &ssh_sha256,
+    .ecdh_vt = &ssh_ecdhkex_m_alg,
+    .extra = &kex_extra_curve25519,
 };
 };
 /* Pre-RFC alias */
 /* Pre-RFC alias */
-const ssh_kex ssh_ec_kex_curve25519_libssh = {
-    "[email protected]", NULL, KEXTYPE_ECDH,
-    &ssh_sha256, &kex_extra_curve25519,
+static const ssh_kex ssh_ec_kex_curve25519_libssh = {
+    .name = "[email protected]",
+    .main_type = KEXTYPE_ECDH,
+    .hash = &ssh_sha256,
+    .ecdh_vt = &ssh_ecdhkex_m_alg,
+    .extra = &kex_extra_curve25519,
 };
 };
-
-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,
+/* GSSAPI variant */
+static const ssh_kex ssh_ec_kex_curve25519_gss = {
+    .name = "gss-curve25519-sha256-" GSS_KRB5_OID_HASH,
+    .main_type = KEXTYPE_GSS_ECDH,
+    .hash = &ssh_sha256,
+    .ecdh_vt = &ssh_ecdhkex_m_alg,
+    .extra = &kex_extra_curve25519,
 };
 };
+
+static const struct eckex_extra kex_extra_curve448 = { ec_curve448 };
 const ssh_kex ssh_ec_kex_curve448 = {
 const ssh_kex ssh_ec_kex_curve448 = {
-    "curve448-sha512", NULL, KEXTYPE_ECDH,
-    &ssh_sha512, &kex_extra_curve448,
+    .name = "curve448-sha512",
+    .main_type = KEXTYPE_ECDH,
+    .hash = &ssh_sha512,
+    .ecdh_vt = &ssh_ecdhkex_m_alg,
+    .extra = &kex_extra_curve448,
 };
 };
 
 
-static const struct eckex_extra kex_extra_nistp256 = {
-    ec_p256,
-    ssh_ecdhkex_w_setup,
-    ssh_ecdhkex_w_cleanup,
-    ssh_ecdhkex_w_getpublic,
-    ssh_ecdhkex_w_getkey,
+static const ecdh_keyalg ssh_ecdhkex_w_alg = {
+    .new = ssh_ecdhkex_w_new,
+    .free = ssh_ecdhkex_w_free,
+    .getpublic = ssh_ecdhkex_w_getpublic,
+    .getkey = ssh_ecdhkex_w_getkey,
+    .description = ssh_ecdhkex_description,
 };
 };
+static const struct eckex_extra kex_extra_nistp256 = { ec_p256 };
 const ssh_kex ssh_ec_kex_nistp256 = {
 const ssh_kex ssh_ec_kex_nistp256 = {
-    "ecdh-sha2-nistp256", NULL, KEXTYPE_ECDH,
-    &ssh_sha256, &kex_extra_nistp256,
+    .name = "ecdh-sha2-nistp256",
+    .main_type = KEXTYPE_ECDH,
+    .hash = &ssh_sha256,
+    .ecdh_vt = &ssh_ecdhkex_w_alg,
+    .extra = &kex_extra_nistp256,
 };
 };
-
-static const struct eckex_extra kex_extra_nistp384 = {
-    ec_p384,
-    ssh_ecdhkex_w_setup,
-    ssh_ecdhkex_w_cleanup,
-    ssh_ecdhkex_w_getpublic,
-    ssh_ecdhkex_w_getkey,
+/* GSSAPI variant */
+static const ssh_kex ssh_ec_kex_nistp256_gss = {
+    .name = "gss-nistp256-sha256-" GSS_KRB5_OID_HASH,
+    .main_type = KEXTYPE_GSS_ECDH,
+    .hash = &ssh_sha256,
+    .ecdh_vt = &ssh_ecdhkex_w_alg,
+    .extra = &kex_extra_nistp256,
 };
 };
+
+static const struct eckex_extra kex_extra_nistp384 = { ec_p384 };
 const ssh_kex ssh_ec_kex_nistp384 = {
 const ssh_kex ssh_ec_kex_nistp384 = {
-    "ecdh-sha2-nistp384", NULL, KEXTYPE_ECDH,
-    &ssh_sha384, &kex_extra_nistp384,
+    .name = "ecdh-sha2-nistp384",
+    .main_type = KEXTYPE_ECDH,
+    .hash = &ssh_sha384,
+    .ecdh_vt = &ssh_ecdhkex_w_alg,
+    .extra = &kex_extra_nistp384,
 };
 };
-
-static const struct eckex_extra kex_extra_nistp521 = {
-    ec_p521,
-    ssh_ecdhkex_w_setup,
-    ssh_ecdhkex_w_cleanup,
-    ssh_ecdhkex_w_getpublic,
-    ssh_ecdhkex_w_getkey,
+/* GSSAPI variant */
+static const ssh_kex ssh_ec_kex_nistp384_gss = {
+    .name = "gss-nistp384-sha384-" GSS_KRB5_OID_HASH,
+    .main_type = KEXTYPE_GSS_ECDH,
+    .hash = &ssh_sha384,
+    .ecdh_vt = &ssh_ecdhkex_w_alg,
+    .extra = &kex_extra_nistp384,
 };
 };
+
+static const struct eckex_extra kex_extra_nistp521 = { ec_p521 };
 const ssh_kex ssh_ec_kex_nistp521 = {
 const ssh_kex ssh_ec_kex_nistp521 = {
-    "ecdh-sha2-nistp521", NULL, KEXTYPE_ECDH,
-    &ssh_sha512, &kex_extra_nistp521,
+    .name = "ecdh-sha2-nistp521",
+    .main_type = KEXTYPE_ECDH,
+    .hash = &ssh_sha512,
+    .ecdh_vt = &ssh_ecdhkex_w_alg,
+    .extra = &kex_extra_nistp521,
+};
+/* GSSAPI variant */
+static const ssh_kex ssh_ec_kex_nistp521_gss = {
+    .name = "gss-nistp521-sha512-" GSS_KRB5_OID_HASH,
+    .main_type = KEXTYPE_GSS_ECDH,
+    .hash = &ssh_sha512,
+    .ecdh_vt = &ssh_ecdhkex_w_alg,
+    .extra = &kex_extra_nistp521,
 };
 };
 
 
 static const ssh_kex *const ec_kex_list[] = {
 static const ssh_kex *const ec_kex_list[] = {
@@ -1858,13 +1958,24 @@ static const ssh_kex *const ec_kex_list[] = {
 
 
 const ssh_kexes ssh_ecdh_kex = { lenof(ec_kex_list), ec_kex_list };
 const ssh_kexes ssh_ecdh_kex = { lenof(ec_kex_list), ec_kex_list };
 
 
+static const ssh_kex *const ec_gss_kex_list[] = {
+    &ssh_ec_kex_curve25519_gss,
+    &ssh_ec_kex_nistp521_gss,
+    &ssh_ec_kex_nistp384_gss,
+    &ssh_ec_kex_nistp256_gss,
+};
+
+const ssh_kexes ssh_gssk5_ecdh_kex = {
+    lenof(ec_gss_kex_list), ec_gss_kex_list
+};
+
 /* ----------------------------------------------------------------------
 /* ----------------------------------------------------------------------
  * Helper functions for finding key algorithms and returning auxiliary
  * Helper functions for finding key algorithms and returning auxiliary
  * data.
  * data.
  */
  */
 
 
 const ssh_keyalg *ec_alg_by_oid(int len, const void *oid,
 const ssh_keyalg *ec_alg_by_oid(int len, const void *oid,
-                                        const struct ec_curve **curve)
+                                const struct ec_curve **curve)
 {
 {
     static const ssh_keyalg *algs_with_oid[] = {
     static const ssh_keyalg *algs_with_oid[] = {
         &ssh_ecdsa_nistp256,
         &ssh_ecdsa_nistp256,

+ 6 - 0
source/putty/crypto/hmac.c

@@ -175,6 +175,7 @@ const ssh2_macalg ssh_hmac_sha256 = {
     /*.setkey =*/ hmac_key,
     /*.setkey =*/ hmac_key,
     /*.start =*/ hmac_start,
     /*.start =*/ hmac_start,
     /*.genresult =*/ hmac_genresult,
     /*.genresult =*/ hmac_genresult,
+    .next_message = nullmac_next_message,
     /*.text_name =*/ hmac_text_name,
     /*.text_name =*/ hmac_text_name,
     /*.name =*/ "hmac-sha2-256",
     /*.name =*/ "hmac-sha2-256",
     /*.etm_name =*/ "[email protected]",
     /*.etm_name =*/ "[email protected]",
@@ -191,6 +192,7 @@ const ssh2_macalg ssh_hmac_md5 = {
     /*.setkey =*/ hmac_key,
     /*.setkey =*/ hmac_key,
     /*.start =*/ hmac_start,
     /*.start =*/ hmac_start,
     /*.genresult =*/ hmac_genresult,
     /*.genresult =*/ hmac_genresult,
+    .next_message = nullmac_next_message,
     /*.text_name =*/ hmac_text_name,
     /*.text_name =*/ hmac_text_name,
     /*.name =*/ "hmac-md5",
     /*.name =*/ "hmac-md5",
     /*.etm_name =*/ "[email protected]",
     /*.etm_name =*/ "[email protected]",
@@ -208,6 +210,7 @@ const ssh2_macalg ssh_hmac_sha1 = {
     /*.setkey =*/ hmac_key,
     /*.setkey =*/ hmac_key,
     /*.start =*/ hmac_start,
     /*.start =*/ hmac_start,
     /*.genresult =*/ hmac_genresult,
     /*.genresult =*/ hmac_genresult,
+    .next_message = nullmac_next_message,
     /*.text_name =*/ hmac_text_name,
     /*.text_name =*/ hmac_text_name,
     /*.name =*/ "hmac-sha1",
     /*.name =*/ "hmac-sha1",
     /*.etm_name =*/ "[email protected]",
     /*.etm_name =*/ "[email protected]",
@@ -225,6 +228,7 @@ const ssh2_macalg ssh_hmac_sha1_96 = {
     /*.setkey =*/ hmac_key,
     /*.setkey =*/ hmac_key,
     /*.start =*/ hmac_start,
     /*.start =*/ hmac_start,
     /*.genresult =*/ hmac_genresult,
     /*.genresult =*/ hmac_genresult,
+    .next_message = nullmac_next_message,
     /*.text_name =*/ hmac_text_name,
     /*.text_name =*/ hmac_text_name,
     /*.name =*/ "hmac-sha1-96",
     /*.name =*/ "hmac-sha1-96",
     /*.etm_name =*/ "[email protected]",
     /*.etm_name =*/ "[email protected]",
@@ -244,6 +248,7 @@ const ssh2_macalg ssh_hmac_sha1_buggy = {
     /*.setkey =*/ hmac_key,
     /*.setkey =*/ hmac_key,
     /*.start =*/ hmac_start,
     /*.start =*/ hmac_start,
     /*.genresult =*/ hmac_genresult,
     /*.genresult =*/ hmac_genresult,
+    .next_message = nullmac_next_message,
     /*.text_name =*/ hmac_text_name,
     /*.text_name =*/ hmac_text_name,
     /*.name =*/ "hmac-sha1",
     /*.name =*/ "hmac-sha1",
     NULL, // WINSCP
     NULL, // WINSCP
@@ -263,6 +268,7 @@ const ssh2_macalg ssh_hmac_sha1_96_buggy = {
     /*.setkey =*/ hmac_key,
     /*.setkey =*/ hmac_key,
     /*.start =*/ hmac_start,
     /*.start =*/ hmac_start,
     /*.genresult =*/ hmac_genresult,
     /*.genresult =*/ hmac_genresult,
+    .next_message = nullmac_next_message,
     /*.text_name =*/ hmac_text_name,
     /*.text_name =*/ hmac_text_name,
     /*.name =*/ "hmac-sha1-96",
     /*.name =*/ "hmac-sha1-96",
     NULL, // WINSCP
     NULL, // WINSCP

+ 8 - 0
source/putty/crypto/mpint.c

@@ -84,6 +84,14 @@ mp_int *mp_new(size_t maxbits)
     return mp_make_sized(words);
     return mp_make_sized(words);
 }
 }
 
 
+mp_int *mp_resize(mp_int *mp, size_t newmaxbits)
+{
+    mp_int *copy = mp_new(newmaxbits);
+    mp_copy_into(copy, mp);
+    mp_free(mp);
+    return copy;
+}
+
 mp_int *mp_from_integer(uintmax_t n)
 mp_int *mp_from_integer(uintmax_t n)
 {
 {
     mp_int *x = mp_make_sized(
     mp_int *x = mp_make_sized(

+ 1160 - 0
source/putty/crypto/openssh-certs.c

@@ -0,0 +1,1160 @@
+/*
+ * Public key type for OpenSSH certificates.
+ */
+
+#include "ssh.h"
+#include "putty.h"
+
+enum {
+    SSH_CERT_TYPE_USER = 1,
+    SSH_CERT_TYPE_HOST = 2,
+};
+
+typedef struct opensshcert_key {
+    strbuf *nonce;
+    uint64_t serial;
+    uint32_t type;
+    strbuf *key_id;
+    strbuf *valid_principals;
+    uint64_t valid_after, valid_before;
+    strbuf *critical_options;
+    strbuf *extensions;
+    strbuf *reserved;
+    strbuf *signature_key;
+    strbuf *signature;
+
+    ssh_key *basekey;
+
+    ssh_key sshk;
+} opensshcert_key;
+
+typedef struct blob_fmt {
+    const unsigned *fmt;
+    size_t len;
+} blob_fmt;
+
+typedef struct opensshcert_extra {
+    /*
+     * OpenSSH certificate formats aren't completely consistent about
+     * the relationship between the public+private blob uploaded to
+     * the agent for the certified key type, and the one for the base
+     * key type. Here we specify the mapping.
+     *
+     * Each of these foo_fmt strings indicates the layout of a
+     * particular version of the key, in the form of an array of
+     * integers together with a length, with each integer describing
+     * one of the components of the key. The integers are defined by
+     * enums, so that they're tightly packed; the general idea is that
+     * if you're converting from one form to another, then you use the
+     * format list for the source format to read out a succession of
+     * SSH strings from the source data and put them in an array
+     * indexed by the integer ids, and then use the list for the
+     * destination format to write the strings out to the destination
+     * in the right (maybe different) order.
+     *
+     * pub_fmt describes the format of the public-key blob for the
+     * base key type, not counting the initial string giving the key
+     * type identifier itself. As far as I know, this always matches
+     * the format of the public-key data appearing in the middle of
+     * the certificate.
+     *
+     * base_ossh_fmt describes the format of the full OpenSSH blob
+     * appearing in the ssh-agent protocol for the base key,
+     * containing the public and private key data.
+     *
+     * cert_ossh_fmt describes the format of the OpenSSH blob for the
+     * certificate key format, beginning just after the certificate
+     * string itself.
+     */
+    blob_fmt pub_fmt, base_ossh_fmt, cert_ossh_fmt;
+
+    /*
+     * The RSA-SHA2 algorithm names have their SSH id set to names
+     * like "rsa-sha2-512-cert-...", which is what will be received in
+     * the KEXINIT algorithm list if a host key in one of those
+     * algorithms is presented. But the _key_ type id that will appear
+     * in the public key blob is "ssh-rsa-cert-...". So we need a
+     * separate field to indicate the key type id we expect to see in
+     * certified public keys, and also the one we want to put back
+     * into the artificial public blob we make to pass to the
+     * constructor for the underlying key.
+     *
+     * (In rsa.c this is managed much more simply, because everything
+     * sharing the same vtable wants the same key type id.)
+     */
+    const char *cert_key_ssh_id, *base_key_ssh_id;
+} opensshcert_extra;
+
+/*
+ * The actual integer arrays defining the per-key blob formats.
+ */
+
+/* DSA is the most orthodox: only the obviously necessary public key
+ * info appears at all, it's in the same order everywhere, and none of
+ * it is repeated unnecessarily */
+enum { DSA_p, DSA_q, DSA_g, DSA_y, DSA_x };
+static const unsigned dsa_pub_fmt[] = { DSA_p, DSA_q, DSA_g, DSA_y };
+static const unsigned dsa_base_ossh_fmt[] = {
+    DSA_p, DSA_q, DSA_g, DSA_y, DSA_x };
+static const unsigned dsa_cert_ossh_fmt[] = { DSA_x };
+
+/* ECDSA is almost as nice, except that it pointlessly mentions the
+ * curve name in the public data, which shouldn't be necessary given
+ * that the SSH key id has already implied it. But at least that's
+ * consistent everywhere. */
+enum { ECDSA_curve, ECDSA_point, ECDSA_exp };
+static const unsigned ecdsa_pub_fmt[] = { ECDSA_curve, ECDSA_point };
+static const unsigned ecdsa_base_ossh_fmt[] = {
+    ECDSA_curve, ECDSA_point, ECDSA_exp };
+static const unsigned ecdsa_cert_ossh_fmt[] = { ECDSA_exp };
+
+/* Ed25519 has the oddity that the private data following the
+ * certificate in the OpenSSH blob is preceded by an extra copy of the
+ * public data, for no obviously necessary reason since that doesn't
+ * happen in any of the rest of these formats */
+enum { EDDSA_point, EDDSA_exp };
+static const unsigned eddsa_pub_fmt[] = { EDDSA_point };
+static const unsigned eddsa_base_ossh_fmt[] = { EDDSA_point, EDDSA_exp };
+static const unsigned eddsa_cert_ossh_fmt[] = { EDDSA_point, EDDSA_exp };
+
+/* And RSA has the quirk that the modulus and exponent are reversed in
+ * the base key type's OpenSSH blob! */
+enum { RSA_e, RSA_n, RSA_d, RSA_p, RSA_q, RSA_iqmp };
+static const unsigned rsa_pub_fmt[] = { RSA_e, RSA_n };
+static const unsigned rsa_base_ossh_fmt[] = {
+    RSA_n, RSA_e, RSA_d, RSA_p, RSA_q, RSA_iqmp };
+static const unsigned rsa_cert_ossh_fmt[] = { RSA_d, RSA_p, RSA_q, RSA_iqmp };
+
+/*
+ * Routines to transform one kind of blob into another based on those
+ * foo_fmt integer arrays.
+ */
+typedef struct BlobTransformer {
+    ptrlen *parts;
+    size_t nparts;
+} BlobTransformer;
+
+#define BLOBTRANS_DECLARE(bt) BlobTransformer bt[1] = { { NULL, 0 } }
+
+static inline void blobtrans_clear(BlobTransformer *bt)
+{
+    sfree(bt->parts);
+    bt->parts = NULL;
+    bt->nparts = 0;
+}
+
+static inline bool blobtrans_read(BlobTransformer *bt, BinarySource *src,
+                                  blob_fmt blob)
+{
+    size_t nparts = bt->nparts;
+    for (size_t i = 0; i < blob.len; i++)
+        if (nparts < blob.fmt[i]+1)
+            nparts = blob.fmt[i]+1;
+
+    if (nparts > bt->nparts) {
+        bt->parts = sresize(bt->parts, nparts, ptrlen);
+        while (bt->nparts < nparts)
+            bt->parts[bt->nparts++] = make_ptrlen(NULL, 0);
+    }
+
+    for (size_t i = 0; i < blob.len; i++) {
+        size_t j = blob.fmt[i];
+        ptrlen part = get_string(src);
+        if (bt->parts[j].ptr) {
+            /*
+             * If the same string appears in both the public blob and
+             * the private data, check they match. (This happens in
+             * Ed25519: an extra copy of the public point string
+             * appears in the certified OpenSSH data after the
+             * certificate and before the private key.)
+             */
+            if (!ptrlen_eq_ptrlen(bt->parts[j], part))
+                return false;
+        }
+        bt->parts[j] = part;
+    }
+
+    return true;
+}
+
+static inline void blobtrans_write(BlobTransformer *bt, BinarySink *bs,
+                                   blob_fmt blob)
+{
+    for (size_t i = 0; i < blob.len; i++) {
+        assert(i < bt->nparts);
+        ptrlen part = bt->parts[blob.fmt[i]];
+        assert(part.ptr);
+        put_stringpl(bs, part);
+    }
+}
+
+/*
+ * Forward declarations.
+ */
+static ssh_key *opensshcert_new_pub(const ssh_keyalg *self, ptrlen pub);
+static ssh_key *opensshcert_new_priv(
+    const ssh_keyalg *self, ptrlen pub, ptrlen priv);
+static ssh_key *opensshcert_new_priv_openssh(
+    const ssh_keyalg *self, BinarySource *src);
+static void opensshcert_freekey(ssh_key *key);
+static char *opensshcert_invalid(ssh_key *key, unsigned flags);
+static void opensshcert_sign(ssh_key *key, ptrlen data, unsigned flags,
+                             BinarySink *bs);
+static bool opensshcert_verify(ssh_key *key, ptrlen sig, ptrlen data);
+static void opensshcert_public_blob(ssh_key *key, BinarySink *bs);
+static void opensshcert_private_blob(ssh_key *key, BinarySink *bs);
+static void opensshcert_openssh_blob(ssh_key *key, BinarySink *bs);
+static void opensshcert_ca_public_blob(ssh_key *key, BinarySink *bs);
+static void opensshcert_cert_id_string(ssh_key *key, BinarySink *bs);
+static SeatDialogText *opensshcert_cert_info(ssh_key *key);
+static bool opensshcert_has_private(ssh_key *key);
+static char *opensshcert_cache_str(ssh_key *key);
+static key_components *opensshcert_components(ssh_key *key);
+static ssh_key *opensshcert_base_key(ssh_key *key);
+static bool opensshcert_check_cert(
+    ssh_key *key, bool host, ptrlen principal, uint64_t time,
+    const ca_options *opts, BinarySink *error);
+static int opensshcert_pubkey_bits(const ssh_keyalg *self, ptrlen blob);
+static unsigned opensshcert_supported_flags(const ssh_keyalg *self);
+static const char *opensshcert_alternate_ssh_id(const ssh_keyalg *self,
+                                                unsigned flags);
+static char *opensshcert_alg_desc(const ssh_keyalg *self);
+static bool opensshcert_variable_size(const ssh_keyalg *self);
+static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self,
+                                                 const ssh_keyalg *base);
+
+/*
+ * Top-level vtables for the certified key formats, defined via a list
+ * macro so I can also make an array of them all.
+ */
+
+#define KEYALG_LIST(X)                                                  \
+    X(ssh_dsa, "ssh-dss", "ssh-dss", dsa)                               \
+    X(ssh_rsa, "ssh-rsa", "ssh-rsa", rsa)                               \
+    X(ssh_rsa_sha256, "rsa-sha2-256", "ssh-rsa", rsa)                   \
+    X(ssh_rsa_sha512, "rsa-sha2-512", "ssh-rsa", rsa)                   \
+    X(ssh_ecdsa_ed25519, "ssh-ed25519", "ssh-ed25519", eddsa)           \
+    X(ssh_ecdsa_nistp256, "ecdsa-sha2-nistp256","ecdsa-sha2-nistp256", ecdsa) \
+    X(ssh_ecdsa_nistp384, "ecdsa-sha2-nistp384","ecdsa-sha2-nistp384", ecdsa) \
+    X(ssh_ecdsa_nistp521, "ecdsa-sha2-nistp521","ecdsa-sha2-nistp521", ecdsa) \
+    /* end of list */
+
+#define KEYALG_DEF(name, ssh_alg_id_prefix, ssh_key_id_prefix, fmt_prefix) \
+    static const struct opensshcert_extra opensshcert_##name##_extra = { \
+        .pub_fmt = { .fmt = fmt_prefix ## _pub_fmt,                     \
+                     .len = lenof(fmt_prefix ## _pub_fmt) },            \
+        .base_ossh_fmt = { .fmt = fmt_prefix ## _base_ossh_fmt,         \
+                           .len = lenof(fmt_prefix ## _base_ossh_fmt) }, \
+        .cert_ossh_fmt = { .fmt = fmt_prefix ## _cert_ossh_fmt,         \
+                           .len = lenof(fmt_prefix ## _cert_ossh_fmt) }, \
+        .cert_key_ssh_id = ssh_key_id_prefix "[email protected]",   \
+        .base_key_ssh_id = ssh_key_id_prefix,                           \
+    };                                                                  \
+                                                                        \
+    const ssh_keyalg opensshcert_##name = {                             \
+        .new_pub = opensshcert_new_pub,                                 \
+        .new_priv = opensshcert_new_priv,                               \
+        .new_priv_openssh = opensshcert_new_priv_openssh,               \
+        .freekey = opensshcert_freekey,                                 \
+        .invalid = opensshcert_invalid,                                 \
+        .sign = opensshcert_sign,                                       \
+        .verify = opensshcert_verify,                                   \
+        .public_blob = opensshcert_public_blob,                         \
+        .private_blob = opensshcert_private_blob,                       \
+        .openssh_blob = opensshcert_openssh_blob,                       \
+        .has_private = opensshcert_has_private,                         \
+        .cache_str = opensshcert_cache_str,                             \
+        .components = opensshcert_components,                           \
+        .base_key = opensshcert_base_key,                               \
+        .ca_public_blob = opensshcert_ca_public_blob,                   \
+        .check_cert = opensshcert_check_cert,                           \
+        .cert_id_string = opensshcert_cert_id_string,                   \
+        .cert_info = opensshcert_cert_info,                             \
+        .pubkey_bits = opensshcert_pubkey_bits,                         \
+        .supported_flags = opensshcert_supported_flags,                 \
+        .alternate_ssh_id = opensshcert_alternate_ssh_id,               \
+        .alg_desc = opensshcert_alg_desc,                               \
+        .variable_size = opensshcert_variable_size,                     \
+        .related_alg = opensshcert_related_alg,                         \
+        .ssh_id = ssh_alg_id_prefix "[email protected]",            \
+        .cache_id = "opensshcert-" ssh_key_id_prefix,                   \
+        .extra = &opensshcert_##name##_extra,                           \
+        .is_certificate = true,                                         \
+        .base_alg = &name,                                              \
+    };
+KEYALG_LIST(KEYALG_DEF)
+#undef KEYALG_DEF
+
+#define KEYALG_LIST_ENTRY(name, algid, keyid, fmt) &opensshcert_##name,
+static const ssh_keyalg *const opensshcert_all_keyalgs[] = {
+    KEYALG_LIST(KEYALG_LIST_ENTRY)
+};
+#undef KEYALG_LIST_ENTRY
+
+static strbuf *get_base_public_blob(BinarySource *src,
+                                    const opensshcert_extra *extra)
+{
+    strbuf *basepub = strbuf_new();
+    put_stringz(basepub, extra->base_key_ssh_id);
+
+    /* Make the base public key blob out of the public key
+     * material in the certificate. This invocation of the
+     * blobtrans system doesn't do any format translation, but it
+     * does ensure that the right amount of data is copied so that
+     * src ends up in the right position to read the remaining
+     * certificate fields. */
+    BLOBTRANS_DECLARE(bt);
+    blobtrans_read(bt, src, extra->pub_fmt);
+    blobtrans_write(bt, BinarySink_UPCAST(basepub), extra->pub_fmt);
+    blobtrans_clear(bt);
+
+    return basepub;
+}
+
+static opensshcert_key *opensshcert_new_shared(
+    const ssh_keyalg *self, ptrlen blob, strbuf **basepub_out)
+{
+    const opensshcert_extra *extra = self->extra;
+
+    BinarySource src[1];
+    BinarySource_BARE_INIT_PL(src, blob);
+
+    /* Check the initial key-type string */
+    if (!ptrlen_eq_string(get_string(src), extra->cert_key_ssh_id))
+        return NULL;
+
+    opensshcert_key *ck = snew(opensshcert_key);
+    memset(ck, 0, sizeof(*ck));
+    ck->sshk.vt = self;
+
+    ck->nonce = strbuf_dup(get_string(src));
+    strbuf *basepub = get_base_public_blob(src, extra);
+    ck->serial = get_uint64(src);
+    ck->type = get_uint32(src);
+    ck->key_id = strbuf_dup(get_string(src));
+    ck->valid_principals = strbuf_dup(get_string(src));
+    ck->valid_after = get_uint64(src);
+    ck->valid_before = get_uint64(src);
+    ck->critical_options = strbuf_dup(get_string(src));
+    ck->extensions = strbuf_dup(get_string(src));
+    ck->reserved = strbuf_dup(get_string(src));
+    ck->signature_key = strbuf_dup(get_string(src));
+    ck->signature = strbuf_dup(get_string(src));
+
+
+    if (get_err(src)) {
+        ssh_key_free(&ck->sshk);
+        strbuf_free(basepub);
+        return NULL;
+    }
+
+    *basepub_out = basepub;
+    return ck;
+}
+
+static ssh_key *opensshcert_new_pub(const ssh_keyalg *self, ptrlen pub)
+{
+    strbuf *basepub;
+    opensshcert_key *ck = opensshcert_new_shared(self, pub, &basepub);
+    if (!ck)
+        return NULL;
+
+    ck->basekey = ssh_key_new_pub(self->base_alg, ptrlen_from_strbuf(basepub));
+    strbuf_free(basepub);
+
+    if (!ck->basekey) {
+        ssh_key_free(&ck->sshk);
+        return NULL;
+    }
+
+    return &ck->sshk;
+}
+
+static ssh_key *opensshcert_new_priv(
+    const ssh_keyalg *self, ptrlen pub, ptrlen priv)
+{
+    strbuf *basepub;
+    opensshcert_key *ck = opensshcert_new_shared(self, pub, &basepub);
+    if (!ck)
+        return NULL;
+
+    ck->basekey = ssh_key_new_priv(self->base_alg,
+                                   ptrlen_from_strbuf(basepub), priv);
+    strbuf_free(basepub);
+
+    if (!ck->basekey) {
+        ssh_key_free(&ck->sshk);
+        return NULL;
+    }
+
+    return &ck->sshk;
+}
+
+static ssh_key *opensshcert_new_priv_openssh(
+    const ssh_keyalg *self, BinarySource *src)
+{
+    const opensshcert_extra *extra = self->extra;
+
+    ptrlen cert = get_string(src);
+
+    strbuf *basepub;
+    opensshcert_key *ck = opensshcert_new_shared(self, cert, &basepub);
+    if (!ck)
+        return NULL;
+
+    strbuf *baseossh = strbuf_new();
+
+    /* Make the base OpenSSH key blob out of the public key blob
+     * returned from opensshcert_new_shared, and the trailing
+     * private data following the certificate */
+    BLOBTRANS_DECLARE(bt);
+
+    BinarySource pubsrc[1];
+    BinarySource_BARE_INIT_PL(pubsrc, ptrlen_from_strbuf(basepub));
+    get_string(pubsrc);            /* skip key type id */
+
+    /* blobtrans_read might fail in this case, because we're reading
+     * from two sources and they might fail to match */
+    bool success = blobtrans_read(bt, pubsrc, extra->pub_fmt) &&
+        blobtrans_read(bt, src, extra->cert_ossh_fmt);
+
+    blobtrans_write(bt, BinarySink_UPCAST(baseossh), extra->base_ossh_fmt);
+    blobtrans_clear(bt);
+
+    if (!success) {
+        ssh_key_free(&ck->sshk);
+        strbuf_free(basepub);
+        strbuf_free(baseossh);
+        return NULL;
+    }
+
+    strbuf_free(basepub);
+
+    BinarySource osshsrc[1];
+    BinarySource_BARE_INIT_PL(osshsrc, ptrlen_from_strbuf(baseossh));
+    ck->basekey = ssh_key_new_priv_openssh(self->base_alg, osshsrc);
+    strbuf_free(baseossh);
+
+    if (!ck->basekey) {
+        ssh_key_free(&ck->sshk);
+        return NULL;
+    }
+
+    return &ck->sshk;
+}
+
+static void opensshcert_freekey(ssh_key *key)
+{
+    opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+
+    /* If this function is called from one of the above constructors
+     * because it failed part way through, we might not have managed
+     * to construct ck->basekey, so it might be NULL. */
+    if (ck->basekey)
+        ssh_key_free(ck->basekey);
+
+    strbuf_free(ck->nonce);
+    strbuf_free(ck->key_id);
+    strbuf_free(ck->valid_principals);
+    strbuf_free(ck->critical_options);
+    strbuf_free(ck->extensions);
+    strbuf_free(ck->reserved);
+    strbuf_free(ck->signature_key);
+    strbuf_free(ck->signature);
+
+    sfree(ck);
+}
+
+static ssh_key *opensshcert_base_key(ssh_key *key)
+{
+    opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+    return ck->basekey;
+}
+
+/*
+ * Make a public key object from the CA public blob, potentially
+ * taking into account that the signature might override the algorithm
+ * name
+ */
+static ssh_key *opensshcert_ca_pub_key(
+    opensshcert_key *ck, ptrlen sig, ptrlen *algname)
+{
+    ptrlen ca_keyblob = ptrlen_from_strbuf(ck->signature_key);
+
+    ptrlen alg_source = sig.ptr ? sig : ca_keyblob;
+    if (algname)
+        *algname = pubkey_blob_to_alg_name(alg_source);
+
+    const ssh_keyalg *ca_alg = pubkey_blob_to_alg(alg_source);
+    if (!ca_alg)
+        return NULL;  /* don't even recognise the certifying key type */
+
+    return ssh_key_new_pub(ca_alg, ca_keyblob);
+}
+
+static void opensshcert_signature_preimage(opensshcert_key *ck, BinarySink *bs)
+{
+    const opensshcert_extra *extra = ck->sshk.vt->extra;
+    put_stringz(bs, extra->cert_key_ssh_id);
+    put_stringpl(bs, ptrlen_from_strbuf(ck->nonce));
+
+    strbuf *basepub = strbuf_new();
+    ssh_key_public_blob(ck->basekey, BinarySink_UPCAST(basepub));
+    BinarySource src[1];
+    BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(basepub));
+    get_string(src); /* skip initial key type string */
+    put_data(bs, get_ptr(src), get_avail(src));
+    strbuf_free(basepub);
+
+    put_uint64(bs, ck->serial);
+    put_uint32(bs, ck->type);
+    put_stringpl(bs, ptrlen_from_strbuf(ck->key_id));
+    put_stringpl(bs, ptrlen_from_strbuf(ck->valid_principals));
+    put_uint64(bs, ck->valid_after);
+    put_uint64(bs, ck->valid_before);
+    put_stringpl(bs, ptrlen_from_strbuf(ck->critical_options));
+    put_stringpl(bs, ptrlen_from_strbuf(ck->extensions));
+    put_stringpl(bs, ptrlen_from_strbuf(ck->reserved));
+    put_stringpl(bs, ptrlen_from_strbuf(ck->signature_key));
+}
+
+static void opensshcert_public_blob(ssh_key *key, BinarySink *bs)
+{
+    opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+
+    opensshcert_signature_preimage(ck, bs);
+    put_stringpl(bs, ptrlen_from_strbuf(ck->signature));
+}
+
+static void opensshcert_private_blob(ssh_key *key, BinarySink *bs)
+{
+    opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+    ssh_key_private_blob(ck->basekey, bs);
+}
+
+static void opensshcert_openssh_blob(ssh_key *key, BinarySink *bs)
+{
+    opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+    const opensshcert_extra *extra = key->vt->extra;
+
+    strbuf *cert = strbuf_new();
+    ssh_key_public_blob(key, BinarySink_UPCAST(cert));
+    put_stringsb(bs, cert);
+
+    strbuf *baseossh = strbuf_new_nm();
+    ssh_key_openssh_blob(ck->basekey, BinarySink_UPCAST(baseossh));
+    BinarySource basesrc[1];
+    BinarySource_BARE_INIT_PL(basesrc, ptrlen_from_strbuf(baseossh));
+
+    BLOBTRANS_DECLARE(bt);
+    blobtrans_read(bt, basesrc, extra->base_ossh_fmt);
+    blobtrans_write(bt, bs, extra->cert_ossh_fmt);
+    blobtrans_clear(bt);
+
+    strbuf_free(baseossh);
+}
+
+static void opensshcert_ca_public_blob(ssh_key *key, BinarySink *bs)
+{
+    opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+    put_datapl(bs, ptrlen_from_strbuf(ck->signature_key));
+}
+
+static void opensshcert_cert_id_string(ssh_key *key, BinarySink *bs)
+{
+    opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+    put_datapl(bs, ptrlen_from_strbuf(ck->key_id));
+}
+
+static bool opensshcert_has_private(ssh_key *key)
+{
+    opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+    return ssh_key_has_private(ck->basekey);
+}
+
+static char *opensshcert_cache_str(ssh_key *key)
+{
+    opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+    return ssh_key_cache_str(ck->basekey);
+}
+
+static void opensshcert_time_to_iso8601(BinarySink *bs, uint64_t time)
+{
+    time_t t = time;
+    char buf[256];
+    put_data(bs, buf, strftime(buf, sizeof(buf),
+                               "%Y-%m-%d %H:%M:%S UTC", gmtime(&t)));
+}
+
+static void opensshcert_string_list_key_components(
+    key_components *kc, strbuf *input, const char *title, const char *title2)
+{
+    BinarySource src[1];
+    BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(input));
+
+    const char *titles[2] = { title, title2 };
+    size_t ntitles = (title2 ? 2 : 1);
+
+    unsigned index = 0;
+    while (get_avail(src)) {
+        for (size_t ti = 0; ti < ntitles; ti++) {
+            ptrlen value = get_string(src);
+            if (get_err(src))
+                break;
+            char *name = dupprintf("%s_%u", titles[ti], index);
+            key_components_add_text_pl(kc, name, value);
+            sfree(name);
+        }
+        index++;
+    }
+}
+
+static key_components *opensshcert_components(ssh_key *key)
+{
+    opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+    key_components *kc = ssh_key_components(ck->basekey);
+    key_components_add_binary(kc, "cert_nonce", ptrlen_from_strbuf(ck->nonce));
+    key_components_add_uint(kc, "cert_serial", ck->serial);
+    switch (ck->type) {
+      case SSH_CERT_TYPE_HOST:
+        key_components_add_text(kc, "cert_type", "host");
+        break;
+      case SSH_CERT_TYPE_USER:
+        key_components_add_text(kc, "cert_type", "user");
+        break;
+      default:
+        key_components_add_uint(kc, "cert_type", ck->type);
+        break;
+    }
+    key_components_add_text(kc, "cert_key_id", ck->key_id->s);
+    opensshcert_string_list_key_components(kc, ck->valid_principals,
+                                           "cert_valid_principal", NULL);
+    key_components_add_uint(kc, "cert_valid_after", ck->valid_after);
+    key_components_add_uint(kc, "cert_valid_before", ck->valid_before);
+    /* Translate the validity period into human-legible dates, but
+     * only if they're not the min/max integer. Rationale: if you see
+     * "584554051223-11-09 07:00:15 UTC" as the expiry time you'll be
+     * as likely to think it's a weird buffer overflow as half a
+     * trillion years in the future! */
+    if (ck->valid_after != 0) {
+        strbuf *date = strbuf_new();
+        opensshcert_time_to_iso8601(BinarySink_UPCAST(date), ck->valid_after);
+        key_components_add_text_pl(kc, "cert_valid_after_date",
+                                   ptrlen_from_strbuf(date));
+        strbuf_free(date);
+    }
+    if (ck->valid_before != 0xFFFFFFFFFFFFFFFF) {
+        strbuf *date = strbuf_new();
+        opensshcert_time_to_iso8601(BinarySink_UPCAST(date), ck->valid_before);
+        key_components_add_text_pl(kc, "cert_valid_before_date",
+                                   ptrlen_from_strbuf(date));
+        strbuf_free(date);
+    }
+    opensshcert_string_list_key_components(kc, ck->critical_options,
+                                           "cert_critical_option",
+                                           "cert_critical_option_data");
+    opensshcert_string_list_key_components(kc, ck->extensions,
+                                           "cert_extension",
+                                           "cert_extension_data");
+    key_components_add_binary(kc, "cert_ca_key", ptrlen_from_strbuf(
+                                  ck->signature_key));
+
+    ptrlen ca_algname;
+    ssh_key *ca_key = opensshcert_ca_pub_key(ck, make_ptrlen(NULL, 0),
+                                             &ca_algname);
+    key_components_add_text_pl(kc, "cert_ca_key_algorithm_id", ca_algname);
+
+    if (ca_key) {
+        key_components *kc_ca_key = ssh_key_components(ca_key);
+        for (size_t i = 0; i < kc_ca_key->ncomponents; i++) {
+            key_component *comp = &kc_ca_key->components[i];
+            char *subname = dupcat("cert_ca_key_", comp->name);
+            key_components_add_copy(kc, subname, comp);
+            sfree(subname);
+        }
+        key_components_free(kc_ca_key);
+        ssh_key_free(ca_key);
+    }
+
+    key_components_add_binary(kc, "cert_ca_sig", ptrlen_from_strbuf(
+                                  ck->signature));
+    return kc;
+}
+
+static SeatDialogText *opensshcert_cert_info(ssh_key *key)
+{
+    opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+    SeatDialogText *text = seat_dialog_text_new();
+    strbuf *tmp = strbuf_new();
+
+    seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+                            "Certificate type");
+    switch (ck->type) {
+      case SSH_CERT_TYPE_HOST:
+        seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
+                                "host key");
+        seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+                                "Valid host names");
+        break;
+      case SSH_CERT_TYPE_USER:
+        seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
+                                "user authentication key");
+        seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+                                "Valid user names");
+        break;
+      default:
+        seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
+                                "unknown type %" PRIu32, ck->type);
+        seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+                                "Valid principals");
+        break;
+    }
+
+    {
+        BinarySource src[1];
+        BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(
+                                      ck->valid_principals));
+        const char *sep = "";
+        strbuf_clear(tmp);
+        while (get_avail(src)) {
+            ptrlen principal = get_string(src);
+            if (get_err(src))
+                break;
+            put_dataz(tmp, sep);
+            sep = ",";
+            put_datapl(tmp, principal);
+        }
+        seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
+                                "%s", tmp->s);
+    }
+
+    seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+                            "Validity period");
+    strbuf_clear(tmp);
+    if (ck->valid_after == 0) {
+        if (ck->valid_before == 0xFFFFFFFFFFFFFFFF) {
+            put_dataz(tmp, "forever");
+        } else {
+            put_dataz(tmp, "until ");
+            opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp),
+                                        ck->valid_before);
+        }
+    } else {
+        if (ck->valid_before == 0xFFFFFFFFFFFFFFFF) {
+            put_dataz(tmp, "after ");
+            opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp),
+                                        ck->valid_after);
+        } else {
+            opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp),
+                                        ck->valid_after);
+            put_dataz(tmp, " - ");
+            opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp),
+                                        ck->valid_before);
+        }
+    }
+    seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", tmp->s);
+
+    /*
+     * List critical options we know about. (This is everything listed
+     * in PROTOCOL.certkeys that isn't specific to U2F/FIDO key types
+     * that PuTTY doesn't currently support.)
+     */
+    {
+        BinarySource src[1];
+        BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(
+                                      ck->critical_options));
+        strbuf_clear(tmp);
+        while (get_avail(src)) {
+            ptrlen key = get_string(src);
+            ptrlen value = get_string(src);
+            if (get_err(src))
+                break;
+            if (ck->type == SSH_CERT_TYPE_USER &&
+                ptrlen_eq_string(key, "source-address")) {
+                BinarySource src2[1];
+                BinarySource_BARE_INIT_PL(src2, value);
+                ptrlen addresslist = get_string(src2);
+                seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+                                        "Permitted client IP addresses");
+                seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
+                                        "%.*s", PTRLEN_PRINTF(addresslist));
+            } else if (ck->type == SSH_CERT_TYPE_USER &&
+                       ptrlen_eq_string(key, "force-command")) {
+                BinarySource src2[1];
+                BinarySource_BARE_INIT_PL(src2, value);
+                ptrlen command = get_string(src2);
+                seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+                                        "Forced remote command");
+                seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
+                                        "%.*s", PTRLEN_PRINTF(command));
+            }
+        }
+    }
+
+    /*
+     * List certificate extensions. Again, we go through everything in
+     * PROTOCOL.certkeys that isn't specific to U2F/FIDO key types.
+     * But we also flip the sense round for user-readability: I think
+     * it's more likely that the typical key will permit all these
+     * things, so we emit no output in that case, and only mention the
+     * things that _aren't_ enabled.
+     */
+
+    bool x11_ok = false, agent_ok = false, portfwd_ok = false;
+    bool pty_ok = false, user_rc_ok = false;
+
+    {
+        BinarySource src[1];
+        BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(
+                                      ck->extensions));
+        while (get_avail(src)) {
+            ptrlen key = get_string(src);
+            /* ptrlen value = */ get_string(src); // nothing needs this yet
+            if (get_err(src))
+                break;
+            if (ptrlen_eq_string(key, "permit-X11-forwarding")) {
+                x11_ok = true;
+            } else if (ptrlen_eq_string(key, "permit-agent-forwarding")) {
+                agent_ok = true;
+            } else if (ptrlen_eq_string(key, "permit-port-forwarding")) {
+                portfwd_ok = true;
+            } else if (ptrlen_eq_string(key, "permit-pty")) {
+                pty_ok = true;
+            } else if (ptrlen_eq_string(key, "permit-user-rc")) {
+                user_rc_ok = true;
+            }
+        }
+    }
+
+    if (ck->type == SSH_CERT_TYPE_USER) {
+        if (!x11_ok) {
+            seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+                                    "X11 forwarding permitted");
+            seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
+        }
+        if (!agent_ok) {
+            seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+                                    "Agent forwarding permitted");
+            seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
+        }
+        if (!portfwd_ok) {
+            seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+                                    "Port forwarding permitted");
+            seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
+        }
+        if (!pty_ok) {
+            seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+                                    "PTY allocation permitted");
+            seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
+        }
+        if (!user_rc_ok) {
+            seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+                                    "Running user ~/.ssh.rc permitted");
+            seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
+        }
+    }
+
+    seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+                            "Certificate ID string");
+    seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
+                            "%s", ck->key_id->s);
+    seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+                            "Certificate serial number");
+    seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
+                            "%" PRIu64, ck->serial);
+
+    char *fp = ssh2_fingerprint_blob(ptrlen_from_strbuf(ck->signature_key),
+                                     SSH_FPTYPE_DEFAULT);
+    seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+                            "Fingerprint of signing CA key");
+    seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", fp);
+    sfree(fp);
+
+    fp = ssh2_fingerprint(key, ssh_fptype_to_cert(SSH_FPTYPE_DEFAULT));
+    seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+                            "Fingerprint including certificate");
+    seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", fp);
+    sfree(fp);
+
+    strbuf_free(tmp);
+    return text;
+}
+
+static int opensshcert_pubkey_bits(const ssh_keyalg *self, ptrlen blob)
+{
+    BinarySource src[1];
+    BinarySource_BARE_INIT_PL(src, blob);
+
+    get_string(src);                   /* key type */
+    get_string(src);                   /* nonce */
+    strbuf *basepub = get_base_public_blob(src, self->extra);
+    int bits = ssh_key_public_bits(
+        self->base_alg, ptrlen_from_strbuf(basepub));
+    strbuf_free(basepub);
+    return bits;
+}
+
+static unsigned opensshcert_supported_flags(const ssh_keyalg *self)
+{
+    return ssh_keyalg_supported_flags(self->base_alg);
+}
+
+static const char *opensshcert_alternate_ssh_id(const ssh_keyalg *self,
+                                                unsigned flags)
+{
+    const char *base_id = ssh_keyalg_alternate_ssh_id(self->base_alg, flags);
+
+    for (size_t i = 0; i < lenof(opensshcert_all_keyalgs); i++) {
+        const ssh_keyalg *alg_i = opensshcert_all_keyalgs[i];
+        if (!strcmp(base_id, alg_i->base_alg->ssh_id))
+            return alg_i->ssh_id;
+    }
+
+    return self->ssh_id;
+}
+
+static char *opensshcert_alg_desc(const ssh_keyalg *self)
+{
+    char *base_desc = ssh_keyalg_desc(self->base_alg);
+    char *our_desc = dupcat(base_desc, " cert");
+    sfree(base_desc);
+    return our_desc;
+}
+
+static bool opensshcert_variable_size(const ssh_keyalg *self)
+{
+    return ssh_keyalg_variable_size(self->base_alg);
+}
+
+static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self,
+                                                 const ssh_keyalg *base)
+{
+    for (size_t i = 0; i < lenof(opensshcert_all_keyalgs); i++) {
+        const ssh_keyalg *alg_i = opensshcert_all_keyalgs[i];
+        if (base == alg_i->base_alg)
+            return alg_i;
+    }
+
+    return self;
+}
+
+static char *opensshcert_invalid(ssh_key *key, unsigned flags)
+{
+    opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+    return ssh_key_invalid(ck->basekey, flags);
+}
+
+static bool opensshcert_check_cert(
+    ssh_key *key, bool host, ptrlen principal, uint64_t time,
+    const ca_options *opts, BinarySink *error)
+{
+    opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+    bool result = false;
+    ssh_key *ca_key = NULL;
+    strbuf *preimage = strbuf_new();
+    BinarySource src[1];
+
+    ptrlen signature = ptrlen_from_strbuf(ck->signature);
+
+    /*
+     * The OpenSSH certificate spec is one-layer only: it explicitly
+     * forbids using a certified key in turn as the CA.
+     *
+     * If it did not, then we'd also have to recursively verify
+     * everything up the CA chain until we reached the ultimate root,
+     * and then make sure _that_ was something we trusted. (Not to
+     * mention that there'd probably be an additional SSH_CERT_TYPE_CA
+     * or some such, and certificate options saying what kinds of
+     * certificate a CA was trusted to sign for, and ...)
+     */
+    ca_key = opensshcert_ca_pub_key(ck, make_ptrlen(NULL, 0), NULL);
+    if (!ca_key) {
+        put_fmt(error, "Certificate's signing key is invalid");
+        goto out;
+    }
+    if (ssh_key_alg(ca_key)->is_certificate) {
+        put_fmt(error, "Certificate is signed with a certified key "
+                "(forbidden by OpenSSH certificate specification)");
+        goto out;
+    }
+
+    /*
+     * Now re-instantiate the key in a way that matches the signature
+     * (i.e. so that if the key is an RSA one we get the right subtype
+     * of RSA).
+     */
+    ssh_key_free(ca_key);
+    ca_key = opensshcert_ca_pub_key(ck, signature, NULL);
+    if (!ca_key) {
+        put_fmt(error, "Certificate's signing key does not match "
+                "signature type");
+        goto out;
+    }
+
+    /* Check which signature algorithm is actually in use, because
+     * that might be a reason to reject the certificate (e.g. ssh-rsa
+     * when we wanted rsa-sha2-*). */
+    const ssh_keyalg *sig_alg = ssh_key_alg(ca_key);
+    if ((sig_alg == &ssh_rsa && !opts->permit_rsa_sha1) ||
+        (sig_alg == &ssh_rsa_sha256 && !opts->permit_rsa_sha256) ||
+        (sig_alg == &ssh_rsa_sha512 && !opts->permit_rsa_sha512)) {
+        put_fmt(error, "Certificate signature uses '%s' signature type "
+                "(forbidden by user configuration)", sig_alg->ssh_id);
+        goto out;
+    }
+
+    opensshcert_signature_preimage(ck, BinarySink_UPCAST(preimage));
+
+    if (!ssh_key_verify(ca_key, signature, ptrlen_from_strbuf(preimage))) {
+        put_fmt(error, "Certificate's signature is invalid");
+        goto out;
+    }
+
+    uint32_t expected_type = host ? SSH_CERT_TYPE_HOST : SSH_CERT_TYPE_USER;
+    if (ck->type != expected_type) {
+        put_fmt(error, "Certificate type is ");
+        switch (ck->type) {
+          case SSH_CERT_TYPE_HOST:
+            put_fmt(error, "host");
+            break;
+          case SSH_CERT_TYPE_USER:
+            put_fmt(error, "user");
+            break;
+          default:
+            put_fmt(error, "unknown value %" PRIu32, ck->type);
+            break;
+        }
+        put_fmt(error, "; expected %s", host ? "host" : "user");
+        goto out;
+    }
+
+    /*
+     * Check the time bounds on the certificate.
+     */
+    if (time < ck->valid_after) {
+        put_fmt(error, "Certificate is not valid until ");
+        opensshcert_time_to_iso8601(BinarySink_UPCAST(error), time);
+        goto out;
+    }
+    if (time >= ck->valid_before) {
+        put_fmt(error, "Certificate expired at ");
+        opensshcert_time_to_iso8601(BinarySink_UPCAST(error), time);
+        goto out;
+    }
+
+    /*
+     * Check that this certificate is for the right thing.
+     *
+     * If valid_principals is a zero-length string then this is
+     * specified to be a carte-blanche certificate valid for any
+     * principal (at least, provided you trust the CA that issued it).
+     */
+    if (ck->valid_principals->len != 0) {
+        BinarySource_BARE_INIT_PL(
+            src, ptrlen_from_strbuf(ck->valid_principals));
+
+        while (get_avail(src)) {
+            ptrlen valid_principal = get_string(src);
+            if (get_err(src)) {
+                put_fmt(error, "Certificate's valid principals list is "
+                        "incorrectly formatted");
+                goto out;
+            }
+            if (ptrlen_eq_ptrlen(valid_principal, principal))
+                goto principal_ok;
+        }
+
+        /*
+         * No valid principal matched. Now go through the list a
+         * second time writing the cert contents into the error
+         * message, so that the user can see at a glance what went
+         * wrong.
+         *
+         * (If you've typed the wrong spelling of the host name, you
+         * really need to see "This cert is for 'foo.example.com' and
+         * I was trying to match it against 'foo'", rather than just
+         * "Computer says no".)
+         */
+        put_fmt(error, "Certificate's %s list [",
+                host ? "hostname" : "username");
+        BinarySource_BARE_INIT_PL(
+            src, ptrlen_from_strbuf(ck->valid_principals));
+        const char *sep = "";
+        while (get_avail(src)) {
+            ptrlen valid_principal = get_string(src);
+            put_fmt(error, "%s\"", sep);
+            put_c_string_literal(error, valid_principal);
+            put_fmt(error, "\"");
+            sep = ", ";
+        }
+        put_fmt(error, "] does not contain expected %s \"",
+                host ? "hostname" : "username");
+        put_c_string_literal(error, principal);
+        put_fmt(error, "\"");
+        goto out;
+      principal_ok:;
+    }
+
+    /*
+     * Check for critical options.
+     */
+    {
+        BinarySource_BARE_INIT_PL(
+            src, ptrlen_from_strbuf(ck->critical_options));
+
+        while (get_avail(src)) {
+            ptrlen option = get_string(src);
+            ptrlen data = get_string(src);
+            if (get_err(src)) {
+                put_fmt(error, "Certificate's critical options list is "
+                        "incorrectly formatted");
+                goto out;
+            }
+
+            /*
+             * If we ever do support any options, this will be where
+             * we insert code to recognise and validate them.
+             *
+             * At present, we implement no critical options at all.
+             * (For host certs, as of 2022-04-20, OpenSSH hasn't
+             * defined any. For user certs, the only SSH server using
+             * this is Uppity, which doesn't support key restrictions
+             * in general.)
+             */
+            (void)data; /* no options supported => no use made of the data */
+
+            /*
+             * Report an unrecognised literal.
+             */
+            put_fmt(error, "Certificate specifies an unsupported critical "
+                    "option \"");
+            put_c_string_literal(error, option);
+            put_fmt(error, "\"");
+            goto out;
+        }
+    }
+
+    /* If we get here without failing any check, accept the certificate! */
+    result = true;
+
+  out:
+    if (ca_key)
+        ssh_key_free(ca_key);
+    strbuf_free(preimage);
+    return result;
+}
+
+static bool opensshcert_verify(ssh_key *key, ptrlen sig, ptrlen data)
+{
+    /* This method is pure *signature* verification; checking the
+     * certificate is done elsewhere. */
+    opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+    return ssh_key_verify(ck->basekey, sig, data);
+}
+
+static void opensshcert_sign(ssh_key *key, ptrlen data, unsigned flags,
+                             BinarySink *bs)
+{
+    opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+    ssh_key_sign(ck->basekey, data, flags, bs);
+}

+ 58 - 9
source/putty/crypto/rsa.c

@@ -76,6 +76,21 @@ RSAKey *BinarySource_get_rsa_ssh1_priv_agent(BinarySource *src)
     return rsa;
     return rsa;
 }
 }
 
 
+void duprsakey(RSAKey *dst, const RSAKey *src)
+{
+    dst->bits = src->bits;
+    dst->bytes = src->bytes;
+    dst->modulus = mp_copy(src->modulus);
+    dst->exponent = mp_copy(src->exponent);
+    dst->private_exponent = src->private_exponent ?
+        mp_copy(src->private_exponent) : NULL;
+    dst->p = mp_copy(src->p);
+    dst->q = mp_copy(src->q);
+    dst->iqmp = mp_copy(src->iqmp);
+    dst->comment = src->comment ? dupstr(src->comment) : NULL;
+    dst->sshk.vt = src->sshk.vt;
+}
+
 bool rsa_ssh1_encrypt(unsigned char *data, int length, RSAKey *key)
 bool rsa_ssh1_encrypt(unsigned char *data, int length, RSAKey *key)
 {
 {
     mp_int *b1, *b2;
     mp_int *b1, *b2;
@@ -541,6 +556,12 @@ static key_components *rsa2_components(ssh_key *key)
     return rsa_components(rsa);
     return rsa_components(rsa);
 }
 }
 
 
+static bool rsa2_has_private(ssh_key *key)
+{
+    RSAKey *rsa = container_of(key, RSAKey, sshk);
+    return rsa->private_exponent != NULL;
+}
+
 static void rsa2_public_blob(ssh_key *key, BinarySink *bs)
 static void rsa2_public_blob(ssh_key *key, BinarySink *bs)
 {
 {
     RSAKey *rsa = container_of(key, RSAKey, sshk);
     RSAKey *rsa = container_of(key, RSAKey, sshk);
@@ -561,7 +582,7 @@ static void rsa2_private_blob(ssh_key *key, BinarySink *bs)
 }
 }
 
 
 static ssh_key *rsa2_new_priv(const ssh_keyalg *self,
 static ssh_key *rsa2_new_priv(const ssh_keyalg *self,
-                               ptrlen pub, ptrlen priv)
+                              ptrlen pub, ptrlen priv)
 {
 {
     BinarySource src[1];
     BinarySource src[1];
     ssh_key *sshk;
     ssh_key *sshk;
@@ -867,6 +888,23 @@ static char *rsa2_invalid(ssh_key *key, unsigned flags)
     return NULL;
     return NULL;
 }
 }
 
 
+static unsigned ssh_rsa_supported_flags(const ssh_keyalg *self)
+{
+    return SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512;
+}
+
+static const char *ssh_rsa_alternate_ssh_id(
+    const ssh_keyalg *self, unsigned flags)
+{
+    if (flags & SSH_AGENT_RSA_SHA2_512)
+        return ssh_rsa_sha512.ssh_id;
+    if (flags & SSH_AGENT_RSA_SHA2_256)
+        return ssh_rsa_sha256.ssh_id;
+    return self->ssh_id;
+}
+
+static char *rsa2_alg_desc(const ssh_keyalg *self) { return dupstr("RSA"); }
+
 static const struct ssh2_rsa_extra
 static const struct ssh2_rsa_extra
     rsa_extra = { 0 },
     rsa_extra = { 0 },
     rsa_sha256_extra = { SSH_AGENT_RSA_SHA2_256 },
     rsa_sha256_extra = { SSH_AGENT_RSA_SHA2_256 },
@@ -885,8 +923,12 @@ static const struct ssh2_rsa_extra
     /*.private_blob =*/ rsa2_private_blob,          \
     /*.private_blob =*/ rsa2_private_blob,          \
     /*.openssh_blob =*/ rsa2_openssh_blob,          \
     /*.openssh_blob =*/ rsa2_openssh_blob,          \
     /*.cache_str =*/ rsa2_cache_str,                \
     /*.cache_str =*/ rsa2_cache_str,                \
+    .has_private = rsa2_has_private,            \
     /*.components =*/ rsa2_components,              \
     /*.components =*/ rsa2_components,              \
-    /*.pubkey_bits =*/ rsa2_pubkey_bits
+    /*.pubkey_bits =*/ rsa2_pubkey_bits             \
+    .base_key = nullkey_base_key,               \
+    .alg_desc = rsa2_alg_desc,                  \
+    .variable_size = nullkey_variable_size_yes,
 #define COMMON_KEYALG_FIELDS2 \
 #define COMMON_KEYALG_FIELDS2 \
     /*.cache_id =*/ "rsa2"
     /*.cache_id =*/ "rsa2"
 
 
@@ -895,8 +937,9 @@ const ssh_keyalg ssh_rsa = {
     COMMON_KEYALG_FIELDS,
     COMMON_KEYALG_FIELDS,
     /*.ssh_id =*/ "ssh-rsa",
     /*.ssh_id =*/ "ssh-rsa",
     COMMON_KEYALG_FIELDS2,
     COMMON_KEYALG_FIELDS2,
+    /*.supported_flags =*/ ssh_rsa_supported_flags,
+    .alternate_ssh_id = ssh_rsa_alternate_ssh_id,
     /*.extra =*/ &rsa_extra,
     /*.extra =*/ &rsa_extra,
-    /*.supported_flags =*/ SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512,
 };
 };
 
 
 const ssh_keyalg ssh_rsa_sha256 = {
 const ssh_keyalg ssh_rsa_sha256 = {
@@ -905,7 +948,8 @@ const ssh_keyalg ssh_rsa_sha256 = {
     /*.ssh_id =*/ "rsa-sha2-256",
     /*.ssh_id =*/ "rsa-sha2-256",
     COMMON_KEYALG_FIELDS2,
     COMMON_KEYALG_FIELDS2,
     /*.extra =*/ &rsa_sha256_extra,
     /*.extra =*/ &rsa_sha256_extra,
-    /*.supported_flags =*/ 0,
+    /*.supported_flags =*/ nullkey_supported_flags,
+    .alternate_ssh_id = nullkey_alternate_ssh_id,
 };
 };
 
 
 const ssh_keyalg ssh_rsa_sha512 = {
 const ssh_keyalg ssh_rsa_sha512 = {
@@ -914,7 +958,8 @@ const ssh_keyalg ssh_rsa_sha512 = {
     /*.ssh_id =*/ "rsa-sha2-512",
     /*.ssh_id =*/ "rsa-sha2-512",
     COMMON_KEYALG_FIELDS2,
     COMMON_KEYALG_FIELDS2,
     /*.extra =*/ &rsa_sha512_extra,
     /*.extra =*/ &rsa_sha512_extra,
-    /*.supported_flags =*/ 0,
+    /*.supported_flags =*/ nullkey_supported_flags,
+    .alternate_ssh_id = nullkey_alternate_ssh_id,
 };
 };
 
 
 RSAKey *ssh_rsakex_newkey(ptrlen data)
 RSAKey *ssh_rsakex_newkey(ptrlen data)
@@ -1130,13 +1175,17 @@ static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha1 = { 1024 };
 static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha256 = { 2048 };
 static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha256 = { 2048 };
 
 
 static const ssh_kex ssh_rsa_kex_sha1 = {
 static const ssh_kex ssh_rsa_kex_sha1 = {
-    "rsa1024-sha1", NULL, KEXTYPE_RSA,
-    &ssh_sha1, &ssh_rsa_kex_extra_sha1,
+    .name = "rsa1024-sha1",
+    .main_type = KEXTYPE_RSA,
+    .hash = &ssh_sha1,
+    .extra = &ssh_rsa_kex_extra_sha1,
 };
 };
 
 
 static const ssh_kex ssh_rsa_kex_sha256 = {
 static const ssh_kex ssh_rsa_kex_sha256 = {
-    "rsa2048-sha256", NULL, KEXTYPE_RSA,
-    &ssh_sha256, &ssh_rsa_kex_extra_sha256,
+    .name = "rsa2048-sha256",
+    .main_type = KEXTYPE_RSA,
+    .hash = &ssh_sha256,
+    .extra = &ssh_rsa_kex_extra_sha256,
 };
 };
 
 
 static const ssh_kex *const rsa_kex_list[] = {
 static const ssh_kex *const rsa_kex_list[] = {

+ 22 - 0
source/putty/defs.h

@@ -127,6 +127,9 @@ typedef struct LogPolicyVtable LogPolicyVtable;
 
 
 typedef struct Seat Seat;
 typedef struct Seat Seat;
 typedef struct SeatVtable SeatVtable;
 typedef struct SeatVtable SeatVtable;
+typedef struct SeatDialogText SeatDialogText;
+typedef struct SeatDialogTextItem SeatDialogTextItem;
+typedef struct SeatDialogPromptDescriptions SeatDialogPromptDescriptions;
 typedef struct SeatPromptResult SeatPromptResult;
 typedef struct SeatPromptResult SeatPromptResult;
 
 
 typedef struct cmdline_get_passwd_input_state cmdline_get_passwd_input_state;
 typedef struct cmdline_get_passwd_input_state cmdline_get_passwd_input_state;
@@ -154,6 +157,8 @@ typedef struct Channel Channel;
 typedef struct SshChannel SshChannel;
 typedef struct SshChannel SshChannel;
 typedef struct mainchan mainchan;
 typedef struct mainchan mainchan;
 
 
+typedef struct CertExprBuilder CertExprBuilder;
+
 typedef struct ssh_sharing_state ssh_sharing_state;
 typedef struct ssh_sharing_state ssh_sharing_state;
 typedef struct ssh_sharing_connstate ssh_sharing_connstate;
 typedef struct ssh_sharing_connstate ssh_sharing_connstate;
 typedef struct share_channel share_channel;
 typedef struct share_channel share_channel;
@@ -180,12 +185,19 @@ typedef struct ssh_cipher ssh_cipher;
 typedef struct ssh2_ciphers ssh2_ciphers;
 typedef struct ssh2_ciphers ssh2_ciphers;
 typedef struct dh_ctx dh_ctx;
 typedef struct dh_ctx dh_ctx;
 typedef struct ecdh_key ecdh_key;
 typedef struct ecdh_key ecdh_key;
+typedef struct ecdh_keyalg ecdh_keyalg;
+typedef struct NTRUKeyPair NTRUKeyPair;
+typedef struct NTRUEncodeSchedule NTRUEncodeSchedule;
 
 
 typedef struct dlgparam dlgparam;
 typedef struct dlgparam dlgparam;
+typedef struct dlgcontrol dlgcontrol;
 
 
 typedef struct settings_w settings_w;
 typedef struct settings_w settings_w;
 typedef struct settings_r settings_r;
 typedef struct settings_r settings_r;
 typedef struct settings_e settings_e;
 typedef struct settings_e settings_e;
+typedef struct ca_options ca_options;
+typedef struct host_ca host_ca;
+typedef struct host_ca_enum host_ca_enum;
 
 
 typedef struct SessionSpecial SessionSpecial;
 typedef struct SessionSpecial SessionSpecial;
 
 
@@ -254,4 +266,14 @@ struct unicode_data;
 #define CAT_INNER(x,y) x ## y
 #define CAT_INNER(x,y) x ## y
 #define CAT(x,y) CAT_INNER(x,y)
 #define CAT(x,y) CAT_INNER(x,y)
 
 
+/*
+ * Structure shared between ssh.h and storage.h, giving strictness
+ * options relating to checking of an OpenSSH certificate. It's a bit
+ * cheaty to put something so specific in here, but more painful to
+ * put it in putty.h.
+ */
+struct ca_options {
+    bool permit_rsa_sha1, permit_rsa_sha256, permit_rsa_sha512;
+};
+
 #endif /* PUTTY_DEFS_H */
 #endif /* PUTTY_DEFS_H */

+ 49 - 51
source/putty/import.c

@@ -180,17 +180,6 @@ bool export_ssh2(const Filename *filename, int type,
     return false;
     return false;
 }
 }
 
 
-/*
- * Strip trailing CRs and LFs at the end of a line of text.
- */
-void strip_crlf(char *str)
-{
-    char *p = str + strlen(str);
-
-    while (p > str && (p[-1] == '\r' || p[-1] == '\n'))
-        *--p = '\0';
-}
-
 /* ----------------------------------------------------------------------
 /* ----------------------------------------------------------------------
  * Helper routines. (The base64 ones are defined in sshpubk.c.)
  * Helper routines. (The base64 ones are defined in sshpubk.c.)
  */
  */
@@ -334,7 +323,7 @@ struct openssh_pem_key {
     strbuf *keyblob;
     strbuf *keyblob;
 };
 };
 
 
-void BinarySink_put_mp_ssh2_from_string(BinarySink *bs, ptrlen str)
+static void BinarySink_put_mp_ssh2_from_string(BinarySink *bs, ptrlen str)
 {
 {
     const unsigned char *bytes = (const unsigned char *)str.ptr;
     const unsigned char *bytes = (const unsigned char *)str.ptr;
     size_t nbytes = str.len;
     size_t nbytes = str.len;
@@ -504,7 +493,7 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src,
     if (errmsg_p) *errmsg_p = NULL;
     if (errmsg_p) *errmsg_p = NULL;
     return ret;
     return ret;
 
 
-    error:
+  error:
     if (line) {
     if (line) {
         smemclr(line, strlen(line));
         smemclr(line, strlen(line));
         sfree(line);
         sfree(line);
@@ -809,7 +798,7 @@ static ssh2_userkey *openssh_pem_read(
     errmsg = NULL;                     /* no error */
     errmsg = NULL;                     /* no error */
     retval = retkey;
     retval = retkey;
 
 
-    error:
+  error:
     strbuf_free(blob);
     strbuf_free(blob);
     strbuf_free(key->keyblob);
     strbuf_free(key->keyblob);
     smemclr(key, sizeof(*key));
     smemclr(key, sizeof(*key));
@@ -819,7 +808,7 @@ static ssh2_userkey *openssh_pem_read(
 }
 }
 
 
 static bool openssh_pem_write(
 static bool openssh_pem_write(
-    const Filename *filename, ssh2_userkey *key, const char *passphrase)
+    const Filename *filename, ssh2_userkey *ukey, const char *passphrase)
 {
 {
     strbuf *pubblob, *privblob, *outblob;
     strbuf *pubblob, *privblob, *outblob;
     unsigned char *spareblob;
     unsigned char *spareblob;
@@ -833,13 +822,17 @@ static bool openssh_pem_write(
     FILE *fp;
     FILE *fp;
     BinarySource src[1];
     BinarySource src[1];
 
 
+    /* OpenSSH's private key files never contain a certificate, so
+     * revert to the underlying base key if necessary */
+    ssh_key *key = ssh_key_base_key(ukey->key);
+
     /*
     /*
      * Fetch the key blobs.
      * Fetch the key blobs.
      */
      */
     pubblob = strbuf_new();
     pubblob = strbuf_new();
-    ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob));
+    ssh_key_public_blob(key, BinarySink_UPCAST(pubblob));
     privblob = strbuf_new_nm();
     privblob = strbuf_new_nm();
-    ssh_key_private_blob(key->key, BinarySink_UPCAST(privblob));
+    ssh_key_private_blob(key, BinarySink_UPCAST(privblob));
     spareblob = NULL;
     spareblob = NULL;
 
 
     outblob = strbuf_new_nm();
     outblob = strbuf_new_nm();
@@ -848,8 +841,8 @@ static bool openssh_pem_write(
      * Encode the OpenSSH key blob, and also decide on the header
      * Encode the OpenSSH key blob, and also decide on the header
      * line.
      * line.
      */
      */
-    if (ssh_key_alg(key->key) == &ssh_rsa ||
-        ssh_key_alg(key->key) == &ssh_dsa) {
+    if (ssh_key_alg(key) == &ssh_rsa ||
+        ssh_key_alg(key) == &ssh_dsa) {
         strbuf *seq;
         strbuf *seq;
 
 
         /*
         /*
@@ -859,7 +852,7 @@ static bool openssh_pem_write(
          * bignums per key type and then construct the actual blob in
          * bignums per key type and then construct the actual blob in
          * common code after that.
          * common code after that.
          */
          */
-        if (ssh_key_alg(key->key) == &ssh_rsa) {
+        if (ssh_key_alg(key) == &ssh_rsa) {
             ptrlen n, e, d, p, q, iqmp, dmp1, dmq1;
             ptrlen n, e, d, p, q, iqmp, dmp1, dmq1;
             mp_int *bd, *bp, *bq, *bdmp1, *bdmq1;
             mp_int *bd, *bp, *bq, *bdmp1, *bdmq1;
 
 
@@ -955,11 +948,11 @@ static bool openssh_pem_write(
         put_ber_id_len(outblob, 16, seq->len, ASN1_CONSTRUCTED);
         put_ber_id_len(outblob, 16, seq->len, ASN1_CONSTRUCTED);
         put_data(outblob, seq->s, seq->len);
         put_data(outblob, seq->s, seq->len);
         strbuf_free(seq);
         strbuf_free(seq);
-    } else if (ssh_key_alg(key->key) == &ssh_ecdsa_nistp256 ||
-               ssh_key_alg(key->key) == &ssh_ecdsa_nistp384 ||
-               ssh_key_alg(key->key) == &ssh_ecdsa_nistp521) {
+    } else if (ssh_key_alg(key) == &ssh_ecdsa_nistp256 ||
+               ssh_key_alg(key) == &ssh_ecdsa_nistp384 ||
+               ssh_key_alg(key) == &ssh_ecdsa_nistp521) {
         const unsigned char *oid;
         const unsigned char *oid;
-        struct ecdsa_key *ec = container_of(key->key, struct ecdsa_key, sshk);
+        struct ecdsa_key *ec = container_of(key, struct ecdsa_key, sshk);
         int oidlen;
         int oidlen;
         int pointlen;
         int pointlen;
         strbuf *seq, *sub;
         strbuf *seq, *sub;
@@ -974,7 +967,7 @@ static bool openssh_pem_write(
          *   [1]
          *   [1]
          *     BIT STRING (0x00 public key point)
          *     BIT STRING (0x00 public key point)
          */
          */
-        oid = ec_alg_oid(ssh_key_alg(key->key), &oidlen);
+        oid = ec_alg_oid(ssh_key_alg(key), &oidlen);
         pointlen = (ec->curve->fieldBits + 7) / 8 * 2;
         pointlen = (ec->curve->fieldBits + 7) / 8 * 2;
 
 
         seq = strbuf_new_nm();
         seq = strbuf_new_nm();
@@ -1006,7 +999,7 @@ static bool openssh_pem_write(
 
 
         /* Append the BIT STRING to the sequence */
         /* Append the BIT STRING to the sequence */
         put_ber_id_len(seq, 1, sub->len,
         put_ber_id_len(seq, 1, sub->len,
-                         ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED);
+                       ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED);
         put_data(seq, sub->s, sub->len);
         put_data(seq, sub->s, sub->len);
         strbuf_free(sub);
         strbuf_free(sub);
 
 
@@ -1083,12 +1076,12 @@ static bool openssh_pem_write(
             fprintf(fp, "%02X", iv[i]);
             fprintf(fp, "%02X", iv[i]);
         fprintf(fp, "\n\n");
         fprintf(fp, "\n\n");
     }
     }
-    base64_encode(fp, outblob->u, outblob->len, 64);
+    base64_encode_fp(fp, ptrlen_from_strbuf(outblob), 64);
     fputs(footer, fp);
     fputs(footer, fp);
     fclose(fp);
     fclose(fp);
     ret = true;
     ret = true;
 
 
-    error:
+  error:
     if (outblob)
     if (outblob)
         strbuf_free(outblob);
         strbuf_free(outblob);
     if (spareblob) {
     if (spareblob) {
@@ -1253,8 +1246,8 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
         ret->kdfopts.bcrypt.rounds = get_uint32(opts);
         ret->kdfopts.bcrypt.rounds = get_uint32(opts);
 
 
         if (get_err(opts)) {
         if (get_err(opts)) {
-          errmsg = "failed to parse bcrypt options string";
-          goto error;
+            errmsg = "failed to parse bcrypt options string";
+            goto error;
         }
         }
         break;
         break;
       }
       }
@@ -1302,7 +1295,7 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
     if (errmsg_p) *errmsg_p = NULL;
     if (errmsg_p) *errmsg_p = NULL;
     return ret;
     return ret;
 
 
-    error:
+  error:
     if (line) {
     if (line) {
         smemclr(line, strlen(line));
         smemclr(line, strlen(line));
         sfree(line);
         sfree(line);
@@ -1492,7 +1485,7 @@ static ssh2_userkey *openssh_new_read(
     retval = retkey;
     retval = retkey;
     retkey = NULL;                     /* prevent the free */
     retkey = NULL;                     /* prevent the free */
 
 
-    error:
+  error:
     if (retkey) {
     if (retkey) {
         sfree(retkey->comment);
         sfree(retkey->comment);
         if (retkey->key)
         if (retkey->key)
@@ -1507,7 +1500,7 @@ static ssh2_userkey *openssh_new_read(
 }
 }
 
 
 static bool openssh_new_write(
 static bool openssh_new_write(
-    const Filename *filename, ssh2_userkey *key, const char *passphrase)
+    const Filename *filename, ssh2_userkey *ukey, const char *passphrase)
 {
 {
     strbuf *pubblob, *privblob, *cblob;
     strbuf *pubblob, *privblob, *cblob;
     int padvalue;
     int padvalue;
@@ -1517,13 +1510,17 @@ static bool openssh_new_write(
     const int bcrypt_rounds = 16;
     const int bcrypt_rounds = 16;
     FILE *fp;
     FILE *fp;
 
 
+    /* OpenSSH's private key files never contain a certificate, so
+     * revert to the underlying base key if necessary */
+    ssh_key *key = ssh_key_base_key(ukey->key);
+
     /*
     /*
      * Fetch the key blobs and find out the lengths of things.
      * Fetch the key blobs and find out the lengths of things.
      */
      */
     pubblob = strbuf_new();
     pubblob = strbuf_new();
-    ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob));
+    ssh_key_public_blob(key, BinarySink_UPCAST(pubblob));
     privblob = strbuf_new_nm();
     privblob = strbuf_new_nm();
-    ssh_key_openssh_blob(key->key, BinarySink_UPCAST(privblob));
+    ssh_key_openssh_blob(key, BinarySink_UPCAST(privblob));
 
 
     /*
     /*
      * Construct the cleartext version of the blob.
      * Construct the cleartext version of the blob.
@@ -1570,11 +1567,11 @@ static bool openssh_new_write(
 
 
         /* Private key. The main private blob goes inline, with no string
         /* Private key. The main private blob goes inline, with no string
          * wrapper. */
          * wrapper. */
-        put_stringz(cpblob, ssh_key_ssh_id(key->key));
+        put_stringz(cpblob, ssh_key_ssh_id(key));
         put_data(cpblob, privblob->s, privblob->len);
         put_data(cpblob, privblob->s, privblob->len);
 
 
         /* Comment. */
         /* Comment. */
-        put_stringz(cpblob, key->comment);
+        put_stringz(cpblob, ukey->comment);
 
 
         /* Pad out the encrypted section. */
         /* Pad out the encrypted section. */
         padvalue = 1;
         padvalue = 1;
@@ -1614,12 +1611,12 @@ static bool openssh_new_write(
     if (!fp)
     if (!fp)
         goto error;
         goto error;
     fputs("-----BEGIN OPENSSH PRIVATE KEY-----\n", fp);
     fputs("-----BEGIN OPENSSH PRIVATE KEY-----\n", fp);
-    base64_encode(fp, cblob->u, cblob->len, 64);
+    base64_encode_fp(fp, ptrlen_from_strbuf(cblob), 64);
     fputs("-----END OPENSSH PRIVATE KEY-----\n", fp);
     fputs("-----END OPENSSH PRIVATE KEY-----\n", fp);
     fclose(fp);
     fclose(fp);
     ret = true;
     ret = true;
 
 
-    error:
+  error:
     if (cblob)
     if (cblob)
         strbuf_free(cblob);
         strbuf_free(cblob);
     if (privblob)
     if (privblob)
@@ -1641,11 +1638,12 @@ static bool openssh_auto_write(
      * assume that anything not in that fixed list is newer, and hence
      * assume that anything not in that fixed list is newer, and hence
      * will use the new format.
      * will use the new format.
      */
      */
-    if (ssh_key_alg(key->key) == &ssh_dsa ||
-        ssh_key_alg(key->key) == &ssh_rsa ||
-        ssh_key_alg(key->key) == &ssh_ecdsa_nistp256 ||
-        ssh_key_alg(key->key) == &ssh_ecdsa_nistp384 ||
-        ssh_key_alg(key->key) == &ssh_ecdsa_nistp521)
+    const ssh_keyalg *alg = ssh_key_alg(ssh_key_base_key(key->key));
+    if (alg == &ssh_dsa ||
+        alg == &ssh_rsa ||
+        alg == &ssh_ecdsa_nistp256 ||
+        alg == &ssh_ecdsa_nistp384 ||
+        alg == &ssh_ecdsa_nistp521)
         return openssh_pem_write(filename, key, passphrase);
         return openssh_pem_write(filename, key, passphrase);
     else
     else
         return openssh_new_write(filename, key, passphrase);
         return openssh_new_write(filename, key, passphrase);
@@ -1853,7 +1851,7 @@ static struct sshcom_key *load_sshcom_key(BinarySource *src,
     if (errmsg_p) *errmsg_p = NULL;
     if (errmsg_p) *errmsg_p = NULL;
     return ret;
     return ret;
 
 
-    error:
+  error:
     if (line) {
     if (line) {
         smemclr(line, strlen(line));
         smemclr(line, strlen(line));
         sfree(line);
         sfree(line);
@@ -1891,7 +1889,7 @@ static bool sshcom_encrypted(BinarySource *filesrc, char **comment)
     if (!ptrlen_eq_string(str, "none"))
     if (!ptrlen_eq_string(str, "none"))
         answer = true;
         answer = true;
 
 
-    done:
+  done:
     if (key) {
     if (key) {
         *comment = dupstr(key->comment);
         *comment = dupstr(key->comment);
         strbuf_free(key->keyblob);
         strbuf_free(key->keyblob);
@@ -1903,7 +1901,7 @@ static bool sshcom_encrypted(BinarySource *filesrc, char **comment)
     return answer;
     return answer;
 }
 }
 
 
-void BinarySink_put_mp_sshcom_from_string(BinarySink *bs, ptrlen str)
+static void BinarySink_put_mp_sshcom_from_string(BinarySink *bs, ptrlen str)
 {
 {
     const unsigned char *bytes = (const unsigned char *)str.ptr;
     const unsigned char *bytes = (const unsigned char *)str.ptr;
     size_t nbytes = str.len;
     size_t nbytes = str.len;
@@ -1987,7 +1985,7 @@ static ssh2_userkey *sshcom_read(
         !memcmp(str.ptr, prefix_rsa, sizeof(prefix_rsa) - 1)) {
         !memcmp(str.ptr, prefix_rsa, sizeof(prefix_rsa) - 1)) {
         type = RSA;
         type = RSA;
     } else if (str.len > sizeof(prefix_dsa) - 1 &&
     } else if (str.len > sizeof(prefix_dsa) - 1 &&
-        !memcmp(str.ptr, prefix_dsa, sizeof(prefix_dsa) - 1)) {
+               !memcmp(str.ptr, prefix_dsa, sizeof(prefix_dsa) - 1)) {
         type = DSA;
         type = DSA;
     } else {
     } else {
         errmsg = "key is of unknown type";
         errmsg = "key is of unknown type";
@@ -2142,7 +2140,7 @@ static ssh2_userkey *sshcom_read(
     errmsg = NULL; /* no error */
     errmsg = NULL; /* no error */
     ret = retkey;
     ret = retkey;
 
 
-    error:
+  error:
     if (blob) {
     if (blob) {
         strbuf_free(blob);
         strbuf_free(blob);
     }
     }
@@ -2316,12 +2314,12 @@ static bool sshcom_write(
         }
         }
         fprintf(fp, "%s\"\n", c);
         fprintf(fp, "%s\"\n", c);
     }
     }
-    base64_encode(fp, outblob->u, outblob->len, 70);
+    base64_encode_fp(fp, ptrlen_from_strbuf(outblob), 70);
     fputs("---- END SSH2 ENCRYPTED PRIVATE KEY ----\n", fp);
     fputs("---- END SSH2 ENCRYPTED PRIVATE KEY ----\n", fp);
     fclose(fp);
     fclose(fp);
     ret = true;
     ret = true;
 
 
-    error:
+  error:
     if (outblob)
     if (outblob)
         strbuf_free(outblob);
         strbuf_free(outblob);
     if (privblob)
     if (privblob)

+ 1 - 1
source/putty/marshal.h

@@ -316,7 +316,7 @@ static inline void BinarySource_INIT__(BinarySource *src, ptrlen data)
 
 
 #define get_err(src) (BinarySource_UPCAST(src)->err)
 #define get_err(src) (BinarySource_UPCAST(src)->err)
 #define get_avail(src) (BinarySource_UPCAST(src)->len - \
 #define get_avail(src) (BinarySource_UPCAST(src)->len - \
-                       BinarySource_UPCAST(src)->pos)
+                        BinarySource_UPCAST(src)->pos)
 #define get_ptr(src)                                                    \
 #define get_ptr(src)                                                    \
     ((const void *)(                                                    \
     ((const void *)(                                                    \
         (const unsigned char *)(BinarySource_UPCAST(src)->data) +       \
         (const unsigned char *)(BinarySource_UPCAST(src)->data) +       \

+ 56 - 5
source/putty/misc.h

@@ -52,6 +52,10 @@ struct strbuf {
 strbuf *strbuf_new(void);
 strbuf *strbuf_new(void);
 strbuf *strbuf_new_nm(void);
 strbuf *strbuf_new_nm(void);
 
 
+/* Helpers to allocate a strbuf containing an existing string */
+strbuf *strbuf_dup(ptrlen string);
+strbuf *strbuf_dup_nm(ptrlen string);
+
 void strbuf_free(strbuf *buf);
 void strbuf_free(strbuf *buf);
 void *strbuf_append(strbuf *buf, size_t len);
 void *strbuf_append(strbuf *buf, size_t len);
 void strbuf_shrink_to(strbuf *buf, size_t new_len);
 void strbuf_shrink_to(strbuf *buf, size_t new_len);
@@ -68,9 +72,9 @@ void strbuf_finalise_agent_query(strbuf *buf);
 wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len);
 wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len);
 wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string);
 wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string);
 char *dup_wc_to_mb_c(int codepage, int flags, const wchar_t *string, int len,
 char *dup_wc_to_mb_c(int codepage, int flags, const wchar_t *string, int len,
-                     const char *defchr, struct unicode_data *ucsdata);
+                     const char *defchr);
 char *dup_wc_to_mb(int codepage, int flags, const wchar_t *string,
 char *dup_wc_to_mb(int codepage, int flags, const wchar_t *string,
-                   const char *defchr, struct unicode_data *ucsdata);
+                   const char *defchr);
 
 
 static inline int toint(unsigned u)
 static inline int toint(unsigned u)
 {
 {
@@ -104,6 +108,20 @@ bool strendswith(const char *s, const char *t);
 
 
 void base64_encode_atom(const unsigned char *data, int n, char *out);
 void base64_encode_atom(const unsigned char *data, int n, char *out);
 int base64_decode_atom(const char *atom, unsigned char *out);
 int base64_decode_atom(const char *atom, unsigned char *out);
+void base64_decode_bs(BinarySink *bs, ptrlen data);
+void base64_decode_fp(FILE *fp, ptrlen data);
+strbuf *base64_decode_sb(ptrlen data);
+void base64_encode_bs(BinarySink *bs, ptrlen data, int cpl);
+void base64_encode_fp(FILE *fp, ptrlen data, int cpl);
+strbuf *base64_encode_sb(ptrlen data, int cpl);
+bool base64_valid(ptrlen data);
+
+void percent_encode_bs(BinarySink *bs, ptrlen data, const char *badchars);
+void percent_encode_fp(FILE *fp, ptrlen data, const char *badchars);
+strbuf *percent_encode_sb(ptrlen data, const char *badchars);
+void percent_decode_bs(BinarySink *bs, ptrlen data);
+void percent_decode_fp(FILE *fp, ptrlen data);
+strbuf *percent_decode_sb(ptrlen data);
 
 
 struct bufchain_granule;
 struct bufchain_granule;
 struct bufchain_tag {
 struct bufchain_tag {
@@ -158,6 +176,21 @@ static inline ptrlen make_ptrlen(const void *ptr, size_t len)
     return pl;
     return pl;
 }
 }
 
 
+static inline const void *ptrlen_end(ptrlen pl)
+{
+    return (const char *)pl.ptr + pl.len;
+}
+
+static inline ptrlen make_ptrlen_startend(const void *startv, const void *endv)
+{
+    const char *start = (const char *)startv, *end = (const char *)endv;
+    assert(end >= start);
+    ptrlen pl;
+    pl.ptr = start;
+    pl.len = end - start;
+    return pl;
+}
+
 static inline ptrlen ptrlen_from_asciz(const char *str)
 static inline ptrlen ptrlen_from_asciz(const char *str)
 {
 {
     return make_ptrlen(str, strlen(str));
     return make_ptrlen(str, strlen(str));
@@ -179,6 +212,8 @@ int ptrlen_strcmp(ptrlen pl1, ptrlen pl2);
 bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail);
 bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail);
 bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail);
 bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail);
 ptrlen ptrlen_get_word(ptrlen *input, const char *separators);
 ptrlen ptrlen_get_word(ptrlen *input, const char *separators);
+bool ptrlen_contains(ptrlen input, const char *characters);
+bool ptrlen_contains_only(ptrlen input, const char *characters);
 char *mkstr(ptrlen pl);
 char *mkstr(ptrlen pl);
 int string_length_for_printf(size_t);
 int string_length_for_printf(size_t);
 /* Derive two printf arguments from a ptrlen, suitable for "%.*s" */
 /* Derive two printf arguments from a ptrlen, suitable for "%.*s" */
@@ -197,6 +232,8 @@ int string_length_for_printf(size_t);
 /* Make a ptrlen out of a constant byte array. */
 /* Make a ptrlen out of a constant byte array. */
 #define PTRLEN_FROM_CONST_BYTES(a) make_ptrlen(a, sizeof(a))
 #define PTRLEN_FROM_CONST_BYTES(a) make_ptrlen(a, sizeof(a))
 
 
+void wordwrap(BinarySink *bs, ptrlen input, size_t maxwid);
+
 /* Wipe sensitive data out of memory that's about to be freed. Simpler
 /* Wipe sensitive data out of memory that's about to be freed. Simpler
  * than memset because we don't need the fill char parameter; also
  * than memset because we don't need the fill char parameter; also
  * attempts (by fiddly use of volatile) to inhibit the compiler from
  * attempts (by fiddly use of volatile) to inhibit the compiler from
@@ -207,9 +244,9 @@ void smemclr(void *b, size_t len);
 /* Compare two fixed-length chunks of memory for equality, without
 /* Compare two fixed-length chunks of memory for equality, without
  * data-dependent control flow (so an attacker with a very accurate
  * data-dependent control flow (so an attacker with a very accurate
  * stopwatch can't try to guess where the first mismatching byte was).
  * stopwatch can't try to guess where the first mismatching byte was).
- * Returns false for mismatch or true for equality (unlike memcmp),
- * hinted at by the 'eq' in the name. */
-bool smemeq(const void *av, const void *bv, size_t len);
+ * Returns 0 for mismatch or 1 for equality (unlike memcmp), hinted at
+ * by the 'eq' in the name. */
+unsigned smemeq(const void *av, const void *bv, size_t len);
 
 
 /* Encode a single UTF-8 character. Assumes that illegal characters
 /* Encode a single UTF-8 character. Assumes that illegal characters
  * (such as things in the surrogate range, or > 0x10FFFF) have already
  * (such as things in the surrogate range, or > 0x10FFFF) have already
@@ -485,4 +522,18 @@ static inline ptrlen ptrlen_from_lf(LoadedFile *lf)
  * is made to handle difficult overlap cases. */
  * is made to handle difficult overlap cases. */
 void memxor(uint8_t *out, const uint8_t *in1, const uint8_t *in2, size_t size);
 void memxor(uint8_t *out, const uint8_t *in1, const uint8_t *in2, size_t size);
 
 
+/* Boolean expressions used in OpenSSH certificate configuration */
+bool cert_expr_valid(const char *expression,
+                     char **error_msg, ptrlen *error_loc);
+bool cert_expr_match_str(const char *expression,
+                         const char *hostname, unsigned port);
+/* Build a certificate expression out of hostname wildcards. Required
+ * to handle legacy configuration from early in development, when
+ * multiple wildcards were stored separately in config, implicitly
+ * ORed together. */
+CertExprBuilder *cert_expr_builder_new(void);
+void cert_expr_builder_free(CertExprBuilder *eb);
+void cert_expr_builder_add(CertExprBuilder *eb, const char *wildcard);
+char *cert_expr_expression(CertExprBuilder *eb);
+
 #endif
 #endif

+ 7 - 0
source/putty/mpint.h

@@ -42,6 +42,13 @@ mp_int *mp_new(size_t maxbits);
 void mp_free(mp_int *);
 void mp_free(mp_int *);
 void mp_clear(mp_int *x);
 void mp_clear(mp_int *x);
 
 
+/*
+ * Resize the physical size of existing mp_int, e.g. so that you have
+ * room to transform it in place to a larger value. Destroys the old
+ * mp_int in the process.
+ */
+mp_int *mp_resize(mp_int *, size_t newmaxbits);
+
 /*
 /*
  * Create mp_ints from various sources: little- and big-endian binary
  * Create mp_ints from various sources: little- and big-endian binary
  * data, an ordinary C unsigned integer type, a decimal or hex string
  * data, an ordinary C unsigned integer type, a decimal or hex string

+ 4 - 0
source/putty/network.h

@@ -410,8 +410,10 @@ void backend_socket_log(Seat *seat, LogContext *logctx,
 typedef struct ProxyStderrBuf {
 typedef struct ProxyStderrBuf {
     char buf[8192];
     char buf[8192];
     size_t size;
     size_t size;
+    const char *prefix;                /* must be statically allocated */
 } ProxyStderrBuf;
 } ProxyStderrBuf;
 void psb_init(ProxyStderrBuf *psb);
 void psb_init(ProxyStderrBuf *psb);
+void psb_set_prefix(ProxyStderrBuf *psb, const char *prefix);
 void log_proxy_stderr(
 void log_proxy_stderr(
     Plug *plug, ProxyStderrBuf *psb, const void *vdata, size_t len);
     Plug *plug, ProxyStderrBuf *psb, const void *vdata, size_t len);
 
 
@@ -442,4 +444,6 @@ struct DeferredSocketOpenerVtable {
 static inline void deferred_socket_opener_free(DeferredSocketOpener *dso)
 static inline void deferred_socket_opener_free(DeferredSocketOpener *dso)
 { dso->vt->free(dso); }
 { dso->vt->free(dso); }
 
 
+DeferredSocketOpener *null_deferred_socket_opener(void);
+
 #endif
 #endif

+ 2 - 1
source/putty/proxy/cproxy.c

@@ -26,7 +26,8 @@ strbuf *chap_response(ptrlen challenge, ptrlen password)
     return sb;
     return sb;
 }
 }
 
 
-void BinarySink_put_hex_data(BinarySink *bs, const void *vptr, size_t len)
+static void BinarySink_put_hex_data(BinarySink *bs, const void *vptr,
+                                    size_t len)
 {
 {
     const unsigned char *p = (const unsigned char *)vptr;
     const unsigned char *p = (const unsigned char *)vptr;
     const char *hexdigits = "0123456789abcdef";
     const char *hexdigits = "0123456789abcdef";

+ 11 - 9
source/putty/proxy/proxy.c

@@ -35,7 +35,7 @@ static void proxy_negotiator_cleanup(ProxySocket *ps)
  * Call this when proxy negotiation is complete, so that this
  * Call this when proxy negotiation is complete, so that this
  * socket can begin working normally.
  * socket can begin working normally.
  */
  */
-void proxy_activate(ProxySocket *ps)
+static void proxy_activate(ProxySocket *ps)
 {
 {
     size_t output_before, output_after;
     size_t output_before, output_after;
 
 
@@ -178,7 +178,7 @@ static void sk_proxy_set_frozen (Socket *s, bool is_frozen)
     sk_set_frozen(ps->sub_socket, is_frozen);
     sk_set_frozen(ps->sub_socket, is_frozen);
 }
 }
 
 
-static const char * sk_proxy_socket_error (Socket *s)
+static const char *sk_proxy_socket_error (Socket *s)
 {
 {
     ProxySocket *ps = container_of(s, ProxySocket, sock);
     ProxySocket *ps = container_of(s, ProxySocket, sock);
     if (ps->error != NULL || ps->sub_socket == NULL) {
     if (ps->error != NULL || ps->sub_socket == NULL) {
@@ -400,8 +400,8 @@ static char *dns_log_msg(const char *host, int addressfamily,
 }
 }
 
 
 SockAddr *name_lookup(const char *host, int port, char **canonicalname,
 SockAddr *name_lookup(const char *host, int port, char **canonicalname,
-                     Conf *conf, int addressfamily, LogContext *logctx,
-                     const char *reason)
+                      Conf *conf, int addressfamily, LogContext *logctx,
+                      const char *reason)
 {
 {
     if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE &&
     if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE &&
         do_proxy_dns(conf) &&
         do_proxy_dns(conf) &&
@@ -516,7 +516,9 @@ Socket *new_connection(SockAddr *addr, const char *hostname,
         char *proxy_canonical_name;
         char *proxy_canonical_name;
         Socket *sret;
         Socket *sret;
 
 
-        if (type == PROXY_SSH &&
+        if ((type == PROXY_SSH_TCPIP ||
+             type == PROXY_SSH_EXEC ||
+             type == PROXY_SSH_SUBSYSTEM) &&
             (sret = sshproxy_new_connection(addr, hostname, port, privport,
             (sret = sshproxy_new_connection(addr, hostname, port, privport,
                                             oobinline, nodelay, keepalive,
                                             oobinline, nodelay, keepalive,
                                             plug, conf, itr)) != NULL)
                                             plug, conf, itr)) != NULL)
@@ -590,10 +592,10 @@ Socket *new_connection(SockAddr *addr, const char *hostname,
 
 
         {
         {
             char *logmsg = dupprintf("Will use %s proxy at %s:%d to connect"
             char *logmsg = dupprintf("Will use %s proxy at %s:%d to connect"
-                                      " to %s:%d", vt->type,
-                                      conf_get_str(conf, CONF_proxy_host),
-                                      conf_get_int(conf, CONF_proxy_port),
-                                      hostname, port);
+                                     " to %s:%d", vt->type,
+                                     conf_get_str(conf, CONF_proxy_host),
+                                     conf_get_int(conf, CONF_proxy_port),
+                                     hostname, port);
             plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0);
             plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0);
             sfree(logmsg);
             sfree(logmsg);
         }
         }

+ 24 - 24
source/putty/proxy/telnet.c

@@ -85,31 +85,31 @@ char *format_telnet_command(SockAddr *addr, int port, Conf *conf,
                 int i = 0;
                 int i = 0;
 
 
                 for (;;) {
                 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++;
                     eo++;
-                    break;
-                  }
-
-                  i++;
-                  v <<= 4;
+                    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;
                 break;
               }
               }

+ 137 - 23
source/putty/putty.h

@@ -266,7 +266,6 @@ struct sesslist {
 };
 };
 
 
 struct unicode_data {
 struct unicode_data {
-    char **uni_tbl;
     bool dbcs_screenfont;
     bool dbcs_screenfont;
     int font_codepage;
     int font_codepage;
     int line_codepage;
     int line_codepage;
@@ -423,9 +422,14 @@ enum {
     KEX_WARN,
     KEX_WARN,
     KEX_DHGROUP1,
     KEX_DHGROUP1,
     KEX_DHGROUP14,
     KEX_DHGROUP14,
+    KEX_DHGROUP15,
+    KEX_DHGROUP16,
+    KEX_DHGROUP17,
+    KEX_DHGROUP18,
     KEX_DHGEX,
     KEX_DHGEX,
     KEX_RSA,
     KEX_RSA,
     KEX_ECDH,
     KEX_ECDH,
+    KEX_NTRU_HYBRID,
     KEX_MAX
     KEX_MAX
 };
 };
 
 
@@ -453,6 +457,7 @@ enum {
     CIPHER_DES,
     CIPHER_DES,
     CIPHER_ARCFOUR,
     CIPHER_ARCFOUR,
     CIPHER_CHACHA20,
     CIPHER_CHACHA20,
+    CIPHER_AESGCM,
     CIPHER_MAX                         /* no. ciphers (inc warn) */
     CIPHER_MAX                         /* no. ciphers (inc warn) */
 };
 };
 
 
@@ -474,7 +479,8 @@ enum {
      * Proxy types.
      * Proxy types.
      */
      */
     PROXY_NONE, PROXY_SOCKS4, PROXY_SOCKS5,
     PROXY_NONE, PROXY_SOCKS4, PROXY_SOCKS5,
-    PROXY_HTTP, PROXY_TELNET, PROXY_CMD, PROXY_SSH,
+    PROXY_HTTP, PROXY_TELNET, PROXY_CMD, PROXY_SSH_TCPIP,
+    PROXY_SSH_EXEC, PROXY_SSH_SUBSYSTEM,
     PROXY_FUZZ
     PROXY_FUZZ
 };
 };
 
 
@@ -1084,6 +1090,24 @@ typedef enum SeatOutputType {
     SEAT_OUTPUT_STDOUT, SEAT_OUTPUT_STDERR
     SEAT_OUTPUT_STDOUT, SEAT_OUTPUT_STDERR
 } SeatOutputType;
 } SeatOutputType;
 
 
+typedef enum SeatDialogTextType {
+    SDT_PARA, SDT_DISPLAY, SDT_SCARY_HEADING,
+    SDT_TITLE, SDT_PROMPT, SDT_BATCH_ABORT,
+    SDT_MORE_INFO_KEY, SDT_MORE_INFO_VALUE_SHORT, SDT_MORE_INFO_VALUE_BLOB
+} SeatDialogTextType;
+struct SeatDialogTextItem {
+    SeatDialogTextType type;
+    char *text;
+};
+struct SeatDialogText {
+    size_t nitems, itemsize;
+    SeatDialogTextItem *items;
+};
+SeatDialogText *seat_dialog_text_new(void);
+void seat_dialog_text_free(SeatDialogText *sdt);
+PRINTF_LIKE(3, 4) void seat_dialog_text_append(
+    SeatDialogText *sdt, SeatDialogTextType type, const char *fmt, ...);
+
 /*
 /*
  * Data type 'Seat', which is an API intended to contain essentially
  * Data type 'Seat', which is an API intended to contain essentially
  * everything that a back end might need to talk to its client for:
  * everything that a back end might need to talk to its client for:
@@ -1258,9 +1282,8 @@ struct SeatVtable {
      */
      */
     SeatPromptResult (*confirm_ssh_host_key)(
     SeatPromptResult (*confirm_ssh_host_key)(
         Seat *seat, const char *host, int port, const char *keytype,
         Seat *seat, const char *host, int port, const char *keytype,
-        char *keystr, const char *keydisp, char **key_fingerprints,
-        bool mismatch, void (*callback)(void *ctx, SeatPromptResult result),
-        void *ctx);
+        char *keystr, SeatDialogText *text, HelpCtx helpctx,
+        void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
 
 
     /*
     /*
      * Check with the seat whether it's OK to use a cryptographic
      * Check with the seat whether it's OK to use a cryptographic
@@ -1287,6 +1310,13 @@ struct SeatVtable {
         Seat *seat, const char *algname, const char *betteralgs,
         Seat *seat, const char *algname, const char *betteralgs,
         void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
         void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
 
 
+    /*
+     * Some snippets of text describing the UI actions in host key
+     * prompts / dialog boxes, to be used in ssh/common.c when it
+     * assembles the full text of those prompts.
+     */
+    const SeatDialogPromptDescriptions *(*prompt_descriptions)(Seat *seat);
+
     /*
     /*
      * Indicates whether the seat is expecting to interact with the
      * Indicates whether the seat is expecting to interact with the
      * user in the UTF-8 character set. (Affects e.g. visual erase
      * user in the UTF-8 character set. (Affects e.g. visual erase
@@ -1408,10 +1438,10 @@ static inline void seat_set_busy_status(Seat *seat, BusyStatus status)
 { seat->vt->set_busy_status(seat, status); }
 { seat->vt->set_busy_status(seat, status); }
 static inline SeatPromptResult seat_confirm_ssh_host_key(
 static inline SeatPromptResult seat_confirm_ssh_host_key(
     InteractionReadySeat iseat, const char *h, int p, const char *ktyp,
     InteractionReadySeat iseat, const char *h, int p, const char *ktyp,
-    char *kstr, const char *kdsp, char **fps, bool mis,
+    char *kstr, SeatDialogText *text, HelpCtx helpctx,
     void (*cb)(void *ctx, SeatPromptResult result), void *ctx)
     void (*cb)(void *ctx, SeatPromptResult result), void *ctx)
 { return iseat.seat->vt->confirm_ssh_host_key(
 { return iseat.seat->vt->confirm_ssh_host_key(
-        iseat.seat, h, p, ktyp, kstr, kdsp, fps, mis, cb, ctx); }
+        iseat.seat, h, p, ktyp, kstr, text, helpctx, cb, ctx); }
 static inline SeatPromptResult seat_confirm_weak_crypto_primitive(
 static inline SeatPromptResult seat_confirm_weak_crypto_primitive(
     InteractionReadySeat iseat, const char *atyp, const char *aname,
     InteractionReadySeat iseat, const char *atyp, const char *aname,
     void (*cb)(void *ctx, SeatPromptResult result), void *ctx)
     void (*cb)(void *ctx, SeatPromptResult result), void *ctx)
@@ -1422,6 +1452,9 @@ static inline SeatPromptResult seat_confirm_weak_cached_hostkey(
     void (*cb)(void *ctx, SeatPromptResult result), void *ctx)
     void (*cb)(void *ctx, SeatPromptResult result), void *ctx)
 { return iseat.seat->vt->confirm_weak_cached_hostkey(
 { return iseat.seat->vt->confirm_weak_cached_hostkey(
         iseat.seat, aname, better, cb, ctx); }
         iseat.seat, aname, better, cb, ctx); }
+static inline const SeatDialogPromptDescriptions *seat_prompt_descriptions(
+    Seat *seat)
+{ return seat->vt->prompt_descriptions(seat); }
 static inline bool seat_is_utf8(Seat *seat)
 static inline bool seat_is_utf8(Seat *seat)
 { return seat->vt->is_utf8(seat); }
 { return seat->vt->is_utf8(seat); }
 static inline void seat_echoedit_update(Seat *seat, bool ec, bool ed)
 static inline void seat_echoedit_update(Seat *seat, bool ec, bool ed)
@@ -1467,6 +1500,12 @@ static inline size_t seat_stderr_pl(Seat *seat, ptrlen data)
 static inline size_t seat_banner_pl(InteractionReadySeat iseat, ptrlen data)
 static inline size_t seat_banner_pl(InteractionReadySeat iseat, ptrlen data)
 { return iseat.seat->vt->banner(iseat.seat, data.ptr, data.len); }
 { return iseat.seat->vt->banner(iseat.seat, data.ptr, data.len); }
 
 
+struct SeatDialogPromptDescriptions {
+    const char *hk_accept_action;
+    const char *hk_connect_once_action;
+    const char *hk_cancel_action, *hk_cancel_action_Participle;
+};
+
 /* In the utils subdir: print a message to the Seat which can't be
 /* In the utils subdir: print a message to the Seat which can't be
  * spoofed by server-supplied auth-time output such as SSH banners */
  * spoofed by server-supplied auth-time output such as SSH banners */
 void seat_antispoof_msg(InteractionReadySeat iseat, const char *msg);
 void seat_antispoof_msg(InteractionReadySeat iseat, const char *msg);
@@ -1494,7 +1533,7 @@ char *nullseat_get_ttymode(Seat *seat, const char *mode);
 void nullseat_set_busy_status(Seat *seat, BusyStatus status);
 void nullseat_set_busy_status(Seat *seat, BusyStatus status);
 SeatPromptResult nullseat_confirm_ssh_host_key(
 SeatPromptResult nullseat_confirm_ssh_host_key(
     Seat *seat, const char *host, int port, const char *keytype,
     Seat *seat, const char *host, int port, const char *keytype,
-    char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch,
+    char *keystr, SeatDialogText *text, HelpCtx helpctx,
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
 SeatPromptResult nullseat_confirm_weak_crypto_primitive(
 SeatPromptResult nullseat_confirm_weak_crypto_primitive(
     Seat *seat, const char *algtype, const char *algname,
     Seat *seat, const char *algtype, const char *algname,
@@ -1502,6 +1541,7 @@ SeatPromptResult nullseat_confirm_weak_crypto_primitive(
 SeatPromptResult nullseat_confirm_weak_cached_hostkey(
 SeatPromptResult nullseat_confirm_weak_cached_hostkey(
     Seat *seat, const char *algname, const char *betteralgs,
     Seat *seat, const char *algname, const char *betteralgs,
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+const SeatDialogPromptDescriptions *nullseat_prompt_descriptions(Seat *seat);
 bool nullseat_is_never_utf8(Seat *seat);
 bool nullseat_is_never_utf8(Seat *seat);
 bool nullseat_is_always_utf8(Seat *seat);
 bool nullseat_is_always_utf8(Seat *seat);
 void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing);
 void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing);
@@ -1509,7 +1549,7 @@ const char *nullseat_get_x_display(Seat *seat);
 bool nullseat_get_windowid(Seat *seat, long *id_out);
 bool nullseat_get_windowid(Seat *seat, long *id_out);
 bool nullseat_get_window_pixel_size(Seat *seat, int *width, int *height);
 bool nullseat_get_window_pixel_size(Seat *seat, int *width, int *height);
 StripCtrlChars *nullseat_stripctrl_new(
 StripCtrlChars *nullseat_stripctrl_new(
-        Seat *seat, BinarySink *bs_out, SeatInteractionContext sic);
+    Seat *seat, BinarySink *bs_out, SeatInteractionContext sic);
 void nullseat_set_trust_status(Seat *seat, bool trusted);
 void nullseat_set_trust_status(Seat *seat, bool trusted);
 bool nullseat_can_set_trust_status_yes(Seat *seat);
 bool nullseat_can_set_trust_status_yes(Seat *seat);
 bool nullseat_can_set_trust_status_no(Seat *seat);
 bool nullseat_can_set_trust_status_no(Seat *seat);
@@ -1529,7 +1569,7 @@ bool nullseat_get_cursor_position(Seat *seat, int *x, int *y);
 void console_connection_fatal(Seat *seat, const char *message);
 void console_connection_fatal(Seat *seat, const char *message);
 SeatPromptResult console_confirm_ssh_host_key(
 SeatPromptResult console_confirm_ssh_host_key(
     Seat *seat, const char *host, int port, const char *keytype,
     Seat *seat, const char *host, int port, const char *keytype,
-    char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch,
+    char *keystr, SeatDialogText *text, HelpCtx helpctx,
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
 SeatPromptResult console_confirm_weak_crypto_primitive(
 SeatPromptResult console_confirm_weak_crypto_primitive(
     Seat *seat, const char *algtype, const char *algname,
     Seat *seat, const char *algtype, const char *algname,
@@ -1538,10 +1578,11 @@ SeatPromptResult console_confirm_weak_cached_hostkey(
     Seat *seat, const char *algname, const char *betteralgs,
     Seat *seat, const char *algname, const char *betteralgs,
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
 StripCtrlChars *console_stripctrl_new(
 StripCtrlChars *console_stripctrl_new(
-        Seat *seat, BinarySink *bs_out, SeatInteractionContext sic);
+    Seat *seat, BinarySink *bs_out, SeatInteractionContext sic);
 void console_set_trust_status(Seat *seat, bool trusted);
 void console_set_trust_status(Seat *seat, bool trusted);
 bool console_can_set_trust_status(Seat *seat);
 bool console_can_set_trust_status(Seat *seat);
 bool console_has_mixed_input_stream(Seat *seat);
 bool console_has_mixed_input_stream(Seat *seat);
+const SeatDialogPromptDescriptions *console_prompt_descriptions(Seat *seat);
 
 
 /*
 /*
  * Other centralised seat functions.
  * Other centralised seat functions.
@@ -1634,6 +1675,17 @@ struct TermWinVtable {
 
 
     void (*refresh)(TermWin *);
     void (*refresh)(TermWin *);
 
 
+    /* request_resize asks the front end if the terminal can please be
+     * resized to (w,h) in characters. The front end MAY call
+     * term_size() in response to tell the terminal its new size
+     * (which MAY be the requested size, or some other size if the
+     * requested one can't be achieved). The front end MAY also not
+     * call term_size() at all. But the front end MUST reply to this
+     * request by calling term_resize_request_completed(), after the
+     * responding resize event has taken place (if any).
+     *
+     * The calls to term_size and term_resize_request_completed may be
+     * synchronous callbacks from within the call to request_resize(). */
     void (*request_resize)(TermWin *, int w, int h);
     void (*request_resize)(TermWin *, int w, int h);
 
 
     void (*set_title)(TermWin *, const char *title, int codepage);
     void (*set_title)(TermWin *, const char *title, int codepage);
@@ -1778,6 +1830,8 @@ NORETURN void cleanup_exit(int);
     X(BOOL, NONE, change_username) /* allow username switching in SSH-2 */ \
     X(BOOL, NONE, change_username) /* allow username switching in SSH-2 */ \
     X(INT, INT, ssh_cipherlist) \
     X(INT, INT, ssh_cipherlist) \
     X(FILENAME, NONE, keyfile) \
     X(FILENAME, NONE, keyfile) \
+    X(FILENAME, NONE, detached_cert) \
+    X(STR, NONE, auth_plugin) \
     /* \
     /* \
      * Which SSH protocol to use. \
      * Which SSH protocol to use. \
      * For historical reasons, the current legal values for CONF_sshprot \
      * For historical reasons, the current legal values for CONF_sshprot \
@@ -1969,6 +2023,7 @@ NORETURN void cleanup_exit(int);
     X(INT, NONE, sshbug_winadj) \
     X(INT, NONE, sshbug_winadj) \
     X(INT, NONE, sshbug_chanreq) \
     X(INT, NONE, sshbug_chanreq) \
     X(INT, NONE, sshbug_dropstart) \
     X(INT, NONE, sshbug_dropstart) \
+    X(INT, NONE, sshbug_filter_kexinit) \
     /*                                                                \
     /*                                                                \
      * ssh_simple means that we promise never to open any channel     \
      * ssh_simple means that we promise never to open any channel     \
      * other than the main one, which means it can safely use a very  \
      * other than the main one, which means it can safely use a very  \
@@ -2137,6 +2192,7 @@ FontSpec *platform_default_fontspec(const char *name);
 Terminal *term_init(Conf *, struct unicode_data *, TermWin *);
 Terminal *term_init(Conf *, struct unicode_data *, TermWin *);
 void term_free(Terminal *);
 void term_free(Terminal *);
 void term_size(Terminal *, int, int, int);
 void term_size(Terminal *, int, int, int);
+void term_resize_request_completed(Terminal *);
 void term_paint(Terminal *, int, int, int, int, bool);
 void term_paint(Terminal *, int, int, int, int, bool);
 void term_scroll(Terminal *, int, int);
 void term_scroll(Terminal *, int, int);
 void term_scroll_to_selection(Terminal *, int);
 void term_scroll_to_selection(Terminal *, int);
@@ -2167,7 +2223,7 @@ char *term_get_ttymode(Terminal *term, const char *mode);
 SeatPromptResult term_get_userpass_input(Terminal *term, prompts_t *p);
 SeatPromptResult term_get_userpass_input(Terminal *term, prompts_t *p);
 void term_set_trust_status(Terminal *term, bool trusted);
 void term_set_trust_status(Terminal *term, bool trusted);
 void term_keyinput(Terminal *, int codepage, const void *buf, int len);
 void term_keyinput(Terminal *, int codepage, const void *buf, int len);
-void term_keyinputw(Terminal *, const wchar_t * widebuf, 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_get_cursor_position(Terminal *term, int *x, int *y);
 void term_setup_window_titles(Terminal *term, const char *title_hostname);
 void term_setup_window_titles(Terminal *term, const char *title_hostname);
 void term_notify_minimised(Terminal *term, bool minimised);
 void term_notify_minimised(Terminal *term, bool minimised);
@@ -2183,7 +2239,9 @@ int format_arrow_key(char *buf, Terminal *term, int xkey,
                      bool shift, bool ctrl, bool alt, bool *consumed_alt);
                      bool shift, bool ctrl, bool alt, bool *consumed_alt);
 int format_function_key(char *buf, Terminal *term, int key_number,
 int format_function_key(char *buf, Terminal *term, int key_number,
                         bool shift, bool ctrl, bool alt, bool *consumed_alt);
                         bool shift, bool ctrl, bool alt, bool *consumed_alt);
-int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key);
+int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key,
+                            bool shift, bool ctrl, bool alt,
+                            bool *consumed_alt);
 int format_numeric_keypad_key(char *buf, Terminal *term, char key,
 int format_numeric_keypad_key(char *buf, Terminal *term, char key,
                               bool shift, bool ctrl);
                               bool shift, bool ctrl);
 
 
@@ -2428,14 +2486,13 @@ bool is_dbcs_leadbyte(int codepage, char byte);
 int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen,
 int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen,
              wchar_t *wcstr, int wclen);
              wchar_t *wcstr, int wclen);
 int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen,
 int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen,
-             char *mbstr, int mblen, const char *defchr,
-             struct unicode_data *ucsdata);
+             char *mbstr, int mblen, const char *defchr);
 wchar_t xlat_uskbd2cyrllic(int ch);
 wchar_t xlat_uskbd2cyrllic(int ch);
 int check_compose(int first, int second);
 int check_compose(int first, int second);
-int decode_codepage(char *cp_name);
+int decode_codepage(const char *cp_name);
 const char *cp_enumerate (int index);
 const char *cp_enumerate (int index);
 const char *cp_name(int codepage);
 const char *cp_name(int codepage);
-void get_unitab(int codepage, wchar_t * unitab, int ftype);
+void get_unitab(int codepage, wchar_t *unitab, int ftype);
 
 
 /*
 /*
  * Exports from wcwidth.c
  * Exports from wcwidth.c
@@ -2579,22 +2636,69 @@ void cmdline_error(const char *, ...) PRINTF_LIKE(1, 2);
  * Exports from config.c.
  * Exports from config.c.
  */
  */
 struct controlbox;
 struct controlbox;
-union control;
-void conf_radiobutton_handler(union control *ctrl, dlgparam *dlg,
+void conf_radiobutton_handler(dlgcontrol *ctrl, dlgparam *dlg,
                               void *data, int event);
                               void *data, int event);
 #define CHECKBOX_INVERT (1<<30)
 #define CHECKBOX_INVERT (1<<30)
-void conf_checkbox_handler(union control *ctrl, dlgparam *dlg,
+void conf_checkbox_handler(dlgcontrol *ctrl, dlgparam *dlg,
                            void *data, int event);
                            void *data, int event);
-void conf_editbox_handler(union control *ctrl, dlgparam *dlg,
+void conf_editbox_handler(dlgcontrol *ctrl, dlgparam *dlg,
                           void *data, int event);
                           void *data, int event);
-void conf_filesel_handler(union control *ctrl, dlgparam *dlg,
+void conf_filesel_handler(dlgcontrol *ctrl, dlgparam *dlg,
                           void *data, int event);
                           void *data, int event);
-void conf_fontsel_handler(union control *ctrl, dlgparam *dlg,
+void conf_fontsel_handler(dlgcontrol *ctrl, dlgparam *dlg,
                           void *data, int event);
                           void *data, int event);
 
 
+struct conf_editbox_handler_type {
+    /* Structure passed as context2 to conf_editbox_handler */
+    enum { EDIT_STR, EDIT_INT, EDIT_FIXEDPOINT } type;
+    union {
+        /*
+         * EDIT_STR means the edit box is connected to a string
+         * field in Conf. No further parameters needed.
+         */
+
+        /*
+         * EDIT_INT means the edit box is connected to an int field in
+         * Conf, and the input string is interpreted as decimal. No
+         * further parameters needed. (But we could add one here later
+         * if for some reason we wanted int fields in hex.)
+         */
+
+        /*
+         * EDIT_FIXEDPOINT means the edit box is connected to an int
+         * field in Conf, but the input string is interpreted as
+         * _floating point_, and converted to/from the output int by
+         * means of a fixed denominator. That is,
+         *
+         *   (floating value in edit box) * denominator = value in Conf
+         */
+        struct {
+            double denominator;
+        };
+    };
+};
+
+extern const struct conf_editbox_handler_type conf_editbox_str;
+extern const struct conf_editbox_handler_type conf_editbox_int;
+#define ED_STR CP(&conf_editbox_str)
+#define ED_INT CP(&conf_editbox_int)
+
 void setup_config_box(struct controlbox *b, bool midsession,
 void setup_config_box(struct controlbox *b, bool midsession,
                       int protocol, int protcfginfo);
                       int protocol, int protcfginfo);
 
 
+void setup_ca_config_box(struct controlbox *b);
+
+/* Platforms provide this to be called from config.c */
+void show_ca_config_box(dlgparam *dlg);
+extern const bool has_ca_config_box; /* false if, e.g., we're PuTTYtel */
+
+/* Visible outside config.c so that platforms can use it to recognise
+ * the proxy type control */
+void proxy_type_handler(dlgcontrol *ctrl, dlgparam *dlg,
+                        void *data, int event);
+/* And then they'll set this flag in its generic.context.i */
+#define PROXY_UI_FLAG_LOCAL 1 /* has a local proxy */
+
 /*
 /*
  * Exports from bidi.c.
  * Exports from bidi.c.
  */
  */
@@ -2830,6 +2934,16 @@ void request_callback_notifications(toplevel_callback_notify_fn_t notify,
                                     void *ctx);
                                     void *ctx);
 #endif
 #endif
 
 
+/*
+ * Facility provided by the platform to spawn a parallel subprocess
+ * and present its stdio via a Socket.
+ *
+ * 'prefix' indicates the prefix that should appear on messages passed
+ * to plug_log to provide stderr output from the process.
+ */
+Socket *platform_start_subprocess(const char *cmd, Plug *plug,
+                                  const char *prefix);
+
 /*
 /*
  * Define no-op macros for the jump list functions, on platforms that
  * Define no-op macros for the jump list functions, on platforms that
  * don't support them. (This is a bit of a hack, and it'd be nicer to
  * don't support them. (This is a bit of a hack, and it'd be nicer to

+ 21 - 2
source/putty/settings.c

@@ -17,6 +17,7 @@
 static const struct keyvalwhere ciphernames[] = {
 static const struct keyvalwhere ciphernames[] = {
     { "aes",        CIPHER_AES,             -1, -1 },
     { "aes",        CIPHER_AES,             -1, -1 },
     { "chacha20",   CIPHER_CHACHA20,        CIPHER_AES, +1 },
     { "chacha20",   CIPHER_CHACHA20,        CIPHER_AES, +1 },
+    { "aesgcm",     CIPHER_AESGCM,          CIPHER_CHACHA20, +1 },
     { "3des",       CIPHER_3DES,            -1, -1 },
     { "3des",       CIPHER_3DES,            -1, -1 },
     { "WARN",       CIPHER_WARN,            -1, -1 },
     { "WARN",       CIPHER_WARN,            -1, -1 },
     { "des",        CIPHER_DES,             -1, -1 },
     { "des",        CIPHER_DES,             -1, -1 },
@@ -28,12 +29,24 @@ static const struct keyvalwhere ciphernames[] = {
  * compatibility warts in load_open_settings(), and should be kept
  * compatibility warts in load_open_settings(), and should be kept
  * in sync with those. */
  * in sync with those. */
 static const struct keyvalwhere kexnames[] = {
 static const struct keyvalwhere kexnames[] = {
+    { "ntru-curve25519",    KEX_NTRU_HYBRID, -1, +1 },
     { "ecdh",               KEX_ECDH,       -1, +1 },
     { "ecdh",               KEX_ECDH,       -1, +1 },
     /* This name is misleading: it covers both SHA-256 and SHA-1 variants */
     /* This name is misleading: it covers both SHA-256 and SHA-1 variants */
     { "dh-gex-sha1",        KEX_DHGEX,      -1, -1 },
     { "dh-gex-sha1",        KEX_DHGEX,      -1, -1 },
+    /* Again, this covers both SHA-256 and SHA-1, despite the name: */
     { "dh-group14-sha1",    KEX_DHGROUP14,  -1, -1 },
     { "dh-group14-sha1",    KEX_DHGROUP14,  -1, -1 },
+    /* This one really is only SHA-1, though: */
     { "dh-group1-sha1",     KEX_DHGROUP1,   KEX_WARN, +1 },
     { "dh-group1-sha1",     KEX_DHGROUP1,   KEX_WARN, +1 },
     { "rsa",                KEX_RSA,        KEX_WARN, -1 },
     { "rsa",                KEX_RSA,        KEX_WARN, -1 },
+    /* Larger fixed DH groups: prefer the larger 15 and 16 over 14,
+     * but by default the even larger 17 and 18 go below 16.
+     * Rationale: diminishing returns of improving the DH strength are
+     * outweighed by increased CPU cost. Group 18 is painful on a slow
+     * machine. Users can override if they need to. */
+    { "dh-group15-sha512",  KEX_DHGROUP15,  KEX_DHGROUP14, -1 },
+    { "dh-group16-sha512",  KEX_DHGROUP16,  KEX_DHGROUP15, -1 },
+    { "dh-group17-sha512",  KEX_DHGROUP17,  KEX_DHGROUP16, +1 },
+    { "dh-group18-sha512",  KEX_DHGROUP18,  KEX_DHGROUP17, +1 },
     { "WARN",               KEX_WARN,       -1, -1 }
     { "WARN",               KEX_WARN,       -1, -1 }
 };
 };
 
 
@@ -624,6 +637,8 @@ void save_open_settings(settings_w *sesskey, Conf *conf)
     write_setting_s(sesskey, "LogHost", conf_get_str(conf, CONF_loghost));
     write_setting_s(sesskey, "LogHost", conf_get_str(conf, CONF_loghost));
     write_setting_b(sesskey, "SSH2DES", conf_get_bool(conf, CONF_ssh2_des_cbc));
     write_setting_b(sesskey, "SSH2DES", conf_get_bool(conf, CONF_ssh2_des_cbc));
     write_setting_filename(sesskey, "PublicKeyFile", conf_get_filename(conf, CONF_keyfile));
     write_setting_filename(sesskey, "PublicKeyFile", conf_get_filename(conf, CONF_keyfile));
+    write_setting_filename(sesskey, "DetachedCertificate", conf_get_filename(conf, CONF_detached_cert));
+    write_setting_s(sesskey, "AuthPlugin", conf_get_str(conf, CONF_auth_plugin));
     write_setting_s(sesskey, "RemoteCommand", conf_get_str(conf, CONF_remote_cmd));
     write_setting_s(sesskey, "RemoteCommand", conf_get_str(conf, CONF_remote_cmd));
     write_setting_b(sesskey, "RFCEnviron", conf_get_bool(conf, CONF_rfc_environ));
     write_setting_b(sesskey, "RFCEnviron", conf_get_bool(conf, CONF_rfc_environ));
     write_setting_b(sesskey, "PassiveTelnet", conf_get_bool(conf, CONF_passive_telnet));
     write_setting_b(sesskey, "PassiveTelnet", conf_get_bool(conf, CONF_passive_telnet));
@@ -771,6 +786,7 @@ void save_open_settings(settings_w *sesskey, Conf *conf)
     write_setting_i(sesskey, "BugWinadj", 2-conf_get_int(conf, CONF_sshbug_winadj));
     write_setting_i(sesskey, "BugWinadj", 2-conf_get_int(conf, CONF_sshbug_winadj));
     write_setting_i(sesskey, "BugChanReq", 2-conf_get_int(conf, CONF_sshbug_chanreq));
     write_setting_i(sesskey, "BugChanReq", 2-conf_get_int(conf, CONF_sshbug_chanreq));
     write_setting_i(sesskey, "BugDropStart", 2-conf_get_int(conf, CONF_sshbug_dropstart));
     write_setting_i(sesskey, "BugDropStart", 2-conf_get_int(conf, CONF_sshbug_dropstart));
+    write_setting_i(sesskey, "BugFilterKexinit", 2-conf_get_int(conf, CONF_sshbug_filter_kexinit));
     write_setting_b(sesskey, "StampUtmp", conf_get_bool(conf, CONF_stamp_utmp));
     write_setting_b(sesskey, "StampUtmp", conf_get_bool(conf, CONF_stamp_utmp));
     write_setting_b(sesskey, "LoginShell", conf_get_bool(conf, CONF_login_shell));
     write_setting_b(sesskey, "LoginShell", conf_get_bool(conf, CONF_login_shell));
     write_setting_b(sesskey, "ScrollbarOnLeft", conf_get_bool(conf, CONF_scrollbar_on_left));
     write_setting_b(sesskey, "ScrollbarOnLeft", conf_get_bool(conf, CONF_scrollbar_on_left));
@@ -970,9 +986,9 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
          * a server which offered it then choked, but we never got
          * a server which offered it then choked, but we never got
          * a server version string or any other reports. */
          * a server version string or any other reports. */
         const char *default_kexes,
         const char *default_kexes,
-                   *normal_default = "ecdh,dh-gex-sha1,dh-group14-sha1,rsa,"
+                   *normal_default = "ecdh,dh-gex-sha1,dh-group18-sha512,dh-group17-sha512,dh-group16-sha512,dh-group15-sha512,dh-group14-sha1,rsa,"
                        "WARN,dh-group1-sha1",
                        "WARN,dh-group1-sha1",
-                   *bugdhgex2_default = "ecdh,dh-group14-sha1,rsa,"
+                   *bugdhgex2_default = "ecdh,dh-group18-sha512,dh-group17-sha512,dh-group16-sha512,dh-group15-sha512,dh-group14-sha1,rsa,"
                        "WARN,dh-group1-sha1,dh-gex-sha1";
                        "WARN,dh-group1-sha1,dh-gex-sha1";
         char *raw;
         char *raw;
         i = 2 - gppi_raw(sesskey, "BugDHGEx2", 0);
         i = 2 - gppi_raw(sesskey, "BugDHGEx2", 0);
@@ -1043,6 +1059,8 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
 #endif
 #endif
     gppb(sesskey, "SshNoShell", false, conf, CONF_ssh_no_shell);
     gppb(sesskey, "SshNoShell", false, conf, CONF_ssh_no_shell);
     gppfile(sesskey, "PublicKeyFile", conf, CONF_keyfile);
     gppfile(sesskey, "PublicKeyFile", conf, CONF_keyfile);
+    gppfile(sesskey, "DetachedCertificate", conf, CONF_detached_cert);
+    gpps(sesskey, "AuthPlugin", "", conf, CONF_auth_plugin);
     gpps(sesskey, "RemoteCommand", "", conf, CONF_remote_cmd);
     gpps(sesskey, "RemoteCommand", "", conf, CONF_remote_cmd);
     gppb(sesskey, "RFCEnviron", false, conf, CONF_rfc_environ);
     gppb(sesskey, "RFCEnviron", false, conf, CONF_rfc_environ);
     gppb(sesskey, "PassiveTelnet", false, conf, CONF_passive_telnet);
     gppb(sesskey, "PassiveTelnet", false, conf, CONF_passive_telnet);
@@ -1251,6 +1269,7 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
     i = gppi_raw(sesskey, "BugWinadj", 0); conf_set_int(conf, CONF_sshbug_winadj, 2-i);
     i = gppi_raw(sesskey, "BugWinadj", 0); conf_set_int(conf, CONF_sshbug_winadj, 2-i);
     i = gppi_raw(sesskey, "BugChanReq", 0); conf_set_int(conf, CONF_sshbug_chanreq, 2-i);
     i = gppi_raw(sesskey, "BugChanReq", 0); conf_set_int(conf, CONF_sshbug_chanreq, 2-i);
     i = gppi_raw(sesskey, "BugDropStart", 1); conf_set_int(conf, CONF_sshbug_dropstart, 2-i);
     i = gppi_raw(sesskey, "BugDropStart", 1); conf_set_int(conf, CONF_sshbug_dropstart, 2-i);
+    i = gppi_raw(sesskey, "BugFilterKexinit", 1); conf_set_int(conf, CONF_sshbug_filter_kexinit, 2-i);
     conf_set_bool(conf, CONF_ssh_simple, false);
     conf_set_bool(conf, CONF_ssh_simple, false);
     gppb(sesskey, "StampUtmp", true, conf, CONF_stamp_utmp);
     gppb(sesskey, "StampUtmp", true, conf, CONF_stamp_utmp);
     gppb(sesskey, "LoginShell", true, conf, CONF_login_shell);
     gppb(sesskey, "LoginShell", true, conf, CONF_login_shell);

+ 254 - 31
source/putty/ssh.h

@@ -521,7 +521,7 @@ struct ec_curve {
 };
 };
 
 
 const ssh_keyalg *ec_alg_by_oid(int len, const void *oid,
 const ssh_keyalg *ec_alg_by_oid(int len, const void *oid,
-                                        const struct ec_curve **curve);
+                                const struct ec_curve **curve);
 const unsigned char *ec_alg_oid(const ssh_keyalg *alg, int *oidlen);
 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_nist_curve_lengths[], n_ec_nist_curve_lengths;
 extern const int ec_ed_curve_lengths[], n_ec_ed_curve_lengths;
 extern const int ec_ed_curve_lengths[], n_ec_ed_curve_lengths;
@@ -548,22 +548,34 @@ struct eddsa_key {
 WeierstrassPoint *ecdsa_public(mp_int *private_key, const ssh_keyalg *alg);
 WeierstrassPoint *ecdsa_public(mp_int *private_key, const ssh_keyalg *alg);
 EdwardsPoint *eddsa_public(mp_int *private_key, const ssh_keyalg *alg);
 EdwardsPoint *eddsa_public(mp_int *private_key, const ssh_keyalg *alg);
 
 
+typedef enum KeyComponentType {
+    KCT_TEXT, KCT_BINARY, KCT_MPINT
+} KeyComponentType;
+typedef struct key_component {
+    char *name;
+    KeyComponentType type;
+    union {
+        strbuf *str;                   /* used for KCT_TEXT and KCT_BINARY */
+        mp_int *mp;                    /* used for KCT_MPINT */
+    };
+} key_component;
 typedef struct key_components {
 typedef struct key_components {
     size_t ncomponents, componentsize;
     size_t ncomponents, componentsize;
-    struct {
-        char *name;
-        bool is_mp_int;
-        union {
-            char *text;
-            mp_int *mp;
-        };
-    } *components;
+    key_component *components;
 } key_components;
 } key_components;
 key_components *key_components_new(void);
 key_components *key_components_new(void);
 void key_components_add_text(key_components *kc,
 void key_components_add_text(key_components *kc,
                              const char *name, const char *value);
                              const char *name, const char *value);
+void key_components_add_text_pl(key_components *kc,
+                                const char *name, ptrlen value);
+void key_components_add_binary(key_components *kc,
+                               const char *name, ptrlen value);
 void key_components_add_mp(key_components *kc,
 void key_components_add_mp(key_components *kc,
                            const char *name, mp_int *value);
                            const char *name, mp_int *value);
+void key_components_add_uint(key_components *kc,
+                             const char *name, uintmax_t value);
+void key_components_add_copy(key_components *kc,
+                             const char *name, const key_component *value);
 void key_components_free(key_components *kc);
 void key_components_free(key_components *kc);
 
 
 /*
 /*
@@ -591,6 +603,7 @@ bool rsa_verify(RSAKey *key);
 void rsa_ssh1_public_blob(BinarySink *bs, RSAKey *key, RsaSsh1Order order);
 void rsa_ssh1_public_blob(BinarySink *bs, RSAKey *key, RsaSsh1Order order);
 int rsa_ssh1_public_blob_len(ptrlen data);
 int rsa_ssh1_public_blob_len(ptrlen data);
 void rsa_ssh1_private_blob_agent(BinarySink *bs, RSAKey *key);
 void rsa_ssh1_private_blob_agent(BinarySink *bs, RSAKey *key);
+void duprsakey(RSAKey *dst, const RSAKey *src);
 void freersapriv(RSAKey *key);
 void freersapriv(RSAKey *key);
 void freersakey(RSAKey *key);
 void freersakey(RSAKey *key);
 key_components *rsa_components(RSAKey *key);
 key_components *rsa_components(RSAKey *key);
@@ -623,21 +636,12 @@ strbuf *ssh_rsakex_encrypt(
 mp_int *ssh_rsakex_decrypt(
 mp_int *ssh_rsakex_decrypt(
     RSAKey *key, const ssh_hashalg *h, ptrlen ciphertext);
     RSAKey *key, const ssh_hashalg *h, ptrlen ciphertext);
 
 
-/*
- * SSH2 ECDH key exchange functions
- */
-const char *ssh_ecdhkex_curve_textname(const ssh_kex *kex);
-ecdh_key *ssh_ecdhkex_newkey(const ssh_kex *kex);
-void ssh_ecdhkex_freekey(ecdh_key *key);
-void ssh_ecdhkex_getpublic(ecdh_key *key, BinarySink *bs);
-mp_int *ssh_ecdhkex_getkey(ecdh_key *key, ptrlen remoteKey);
-
 /*
 /*
  * Helper function for k generation in DSA, reused in ECDSA
  * Helper function for k generation in DSA, reused in ECDSA
  */
  */
 mp_int *dsa_gen_k(const char *id_string,
 mp_int *dsa_gen_k(const char *id_string,
-                     mp_int *modulus, mp_int *private_key,
-                     unsigned char *digest, int digest_len);
+                  mp_int *modulus, mp_int *private_key,
+                  unsigned char *digest, int digest_len);
 #endif
 #endif
 
 
 struct ssh_cipher {
 struct ssh_cipher {
@@ -669,6 +673,9 @@ struct ssh_cipheralg {
                            unsigned long seq);
                            unsigned long seq);
     void (*decrypt_length)(ssh_cipher *, void *blk, int len,
     void (*decrypt_length)(ssh_cipher *, void *blk, int len,
                            unsigned long seq);
                            unsigned long seq);
+    /* For ciphers that update their state per logical message
+     * (typically, per unit independently MACed) */
+    void (*next_message)(ssh_cipher *);
     const char *ssh2_id;
     const char *ssh2_id;
     int blksize;
     int blksize;
     /* real_keybits is the number of bits of entropy genuinely used by
     /* real_keybits is the number of bits of entropy genuinely used by
@@ -715,9 +722,13 @@ static inline void ssh_cipher_encrypt_length(
 static inline void ssh_cipher_decrypt_length(
 static inline void ssh_cipher_decrypt_length(
     ssh_cipher *c, void *blk, int len, unsigned long seq)
     ssh_cipher *c, void *blk, int len, unsigned long seq)
 { c->vt->decrypt_length(c, blk, len, seq); }
 { c->vt->decrypt_length(c, blk, len, seq); }
+static inline void ssh_cipher_next_message(ssh_cipher *c)
+{ c->vt->next_message(c); }
 static inline const struct ssh_cipheralg *ssh_cipher_alg(ssh_cipher *c)
 static inline const struct ssh_cipheralg *ssh_cipher_alg(ssh_cipher *c)
 { return c->vt; }
 { return c->vt; }
 
 
+void nullcipher_next_message(ssh_cipher *);
+
 struct ssh2_ciphers {
 struct ssh2_ciphers {
     int nciphers;
     int nciphers;
     const ssh_cipheralg *const *list;
     const ssh_cipheralg *const *list;
@@ -735,6 +746,7 @@ struct ssh2_macalg {
     void (*setkey)(ssh2_mac *, ptrlen key);
     void (*setkey)(ssh2_mac *, ptrlen key);
     void (*start)(ssh2_mac *);
     void (*start)(ssh2_mac *);
     void (*genresult)(ssh2_mac *, unsigned char *);
     void (*genresult)(ssh2_mac *, unsigned char *);
+    void (*next_message)(ssh2_mac *);
     const char *(*text_name)(ssh2_mac *);
     const char *(*text_name)(ssh2_mac *);
     const char *name, *etm_name;
     const char *name, *etm_name;
     int len, keylen;
     int len, keylen;
@@ -754,6 +766,8 @@ static inline void ssh2_mac_start(ssh2_mac *m)
 { m->vt->start(m); }
 { m->vt->start(m); }
 static inline void ssh2_mac_genresult(ssh2_mac *m, unsigned char *out)
 static inline void ssh2_mac_genresult(ssh2_mac *m, unsigned char *out)
 { m->vt->genresult(m, out); }
 { m->vt->genresult(m, out); }
+static inline void ssh2_mac_next_message(ssh2_mac *m)
+{ m->vt->next_message(m); }
 static inline const char *ssh2_mac_text_name(ssh2_mac *m)
 static inline const char *ssh2_mac_text_name(ssh2_mac *m)
 { return m->vt->text_name(m); }
 { return m->vt->text_name(m); }
 static inline const ssh2_macalg *ssh2_mac_alg(ssh2_mac *m)
 static inline const ssh2_macalg *ssh2_mac_alg(ssh2_mac *m)
@@ -766,6 +780,8 @@ bool ssh2_mac_verresult(ssh2_mac *, const void *);
 void ssh2_mac_generate(ssh2_mac *, void *, int, unsigned long seq);
 void ssh2_mac_generate(ssh2_mac *, void *, int, unsigned long seq);
 bool ssh2_mac_verify(ssh2_mac *, const void *, int, unsigned long seq);
 bool ssh2_mac_verify(ssh2_mac *, const void *, int, unsigned long seq);
 
 
+void nullmac_next_message(ssh2_mac *m);
+
 /* Use a MAC in its raw form, outside SSH-2 context, to MAC a given
 /* Use a MAC in its raw form, outside SSH-2 context, to MAC a given
  * string with a given key in the most obvious way. */
  * string with a given key in the most obvious way. */
 void mac_simple(const ssh2_macalg *alg, ptrlen key, ptrlen data, void *output);
 void mac_simple(const ssh2_macalg *alg, ptrlen key, ptrlen data, void *output);
@@ -832,11 +848,20 @@ void hash_simple(const ssh_hashalg *alg, ptrlen data, void *output);
 
 
 struct ssh_kex {
 struct ssh_kex {
     const char *name, *groupname;
     const char *name, *groupname;
-    enum { KEXTYPE_DH, KEXTYPE_RSA, KEXTYPE_ECDH, KEXTYPE_GSS } main_type;
+    enum { KEXTYPE_DH, KEXTYPE_RSA, KEXTYPE_ECDH,
+           KEXTYPE_GSS, KEXTYPE_GSS_ECDH } main_type;
     const ssh_hashalg *hash;
     const ssh_hashalg *hash;
+    union {                  /* publicly visible data for each type */
+        const ecdh_keyalg *ecdh_vt;    /* for KEXTYPE_ECDH, KEXTYPE_GSS_ECDH */
+    };
     const void *extra;                 /* private to the kex methods */
     const void *extra;                 /* private to the kex methods */
 };
 };
 
 
+static inline bool kex_is_gss(const struct ssh_kex *kex)
+{
+    return kex->main_type == KEXTYPE_GSS || kex->main_type == KEXTYPE_GSS_ECDH;
+}
+
 struct ssh_kexes {
 struct ssh_kexes {
     int nkexes;
     int nkexes;
     const ssh_kex *const *list;
     const ssh_kex *const *list;
@@ -864,17 +889,34 @@ struct ssh_keyalg {
     void (*public_blob)(ssh_key *key, BinarySink *);
     void (*public_blob)(ssh_key *key, BinarySink *);
     void (*private_blob)(ssh_key *key, BinarySink *);
     void (*private_blob)(ssh_key *key, BinarySink *);
     void (*openssh_blob) (ssh_key *key, BinarySink *);
     void (*openssh_blob) (ssh_key *key, BinarySink *);
+    bool (*has_private) (ssh_key *key);
     char *(*cache_str) (ssh_key *key);
     char *(*cache_str) (ssh_key *key);
     key_components *(*components) (ssh_key *key);
     key_components *(*components) (ssh_key *key);
+    ssh_key *(*base_key) (ssh_key *key); /* does not confer ownership */
+    /* The following methods can be NULL if !is_certificate */
+    void (*ca_public_blob)(ssh_key *key, BinarySink *);
+    bool (*check_cert)(ssh_key *key, bool host, ptrlen principal,
+                       uint64_t time, const ca_options *opts,
+                       BinarySink *error);
+    void (*cert_id_string)(ssh_key *key, BinarySink *);
+    SeatDialogText *(*cert_info)(ssh_key *key);
 
 
     /* 'Class methods' that don't deal with an ssh_key at all */
     /* 'Class methods' that don't deal with an ssh_key at all */
     int (*pubkey_bits) (const ssh_keyalg *self, ptrlen blob);
     int (*pubkey_bits) (const ssh_keyalg *self, ptrlen blob);
+    unsigned (*supported_flags) (const ssh_keyalg *self);
+    const char *(*alternate_ssh_id) (const ssh_keyalg *self, unsigned flags);
+    char *(*alg_desc)(const ssh_keyalg *self);
+    bool (*variable_size)(const ssh_keyalg *self);
+    /* The following methods can be NULL if !is_certificate */
+    const ssh_keyalg *(*related_alg)(const ssh_keyalg *self,
+                                     const ssh_keyalg *base);
 
 
     /* Constant data fields giving information about the key type */
     /* Constant data fields giving information about the key type */
     const char *ssh_id;    /* string identifier in the SSH protocol */
     const char *ssh_id;    /* string identifier in the SSH protocol */
     const char *cache_id;  /* identifier used in PuTTY's host key cache */
     const char *cache_id;  /* identifier used in PuTTY's host key cache */
     const void *extra;     /* private to the public key methods */
     const void *extra;     /* private to the public key methods */
-    const unsigned supported_flags;    /* signature-type flags we understand */
+    bool is_certificate;   /* is this a certified key type? */
+    const ssh_keyalg *base_alg; /* if so, for what underlying key alg? */
 };
 };
 #pragma option pop // WINSCP
 #pragma option pop // WINSCP
 
 
@@ -901,10 +943,24 @@ static inline void ssh_key_private_blob(ssh_key *key, BinarySink *bs)
 { key->vt->private_blob(key, bs); }
 { key->vt->private_blob(key, bs); }
 static inline void ssh_key_openssh_blob(ssh_key *key, BinarySink *bs)
 static inline void ssh_key_openssh_blob(ssh_key *key, BinarySink *bs)
 { key->vt->openssh_blob(key, bs); }
 { key->vt->openssh_blob(key, bs); }
+static inline bool ssh_key_has_private(ssh_key *key)
+{ return key->vt->has_private(key); }
 static inline char *ssh_key_cache_str(ssh_key *key)
 static inline char *ssh_key_cache_str(ssh_key *key)
 { return key->vt->cache_str(key); }
 { return key->vt->cache_str(key); }
 static inline key_components *ssh_key_components(ssh_key *key)
 static inline key_components *ssh_key_components(ssh_key *key)
 { return key->vt->components(key); }
 { return key->vt->components(key); }
+static inline ssh_key *ssh_key_base_key(ssh_key *key)
+{ return key->vt->base_key(key); }
+static inline void ssh_key_ca_public_blob(ssh_key *key, BinarySink *bs)
+{ key->vt->ca_public_blob(key, bs); }
+static inline void ssh_key_cert_id_string(ssh_key *key, BinarySink *bs)
+{ key->vt->cert_id_string(key, bs); }
+static inline SeatDialogText *ssh_key_cert_info(ssh_key *key)
+{ return key->vt->cert_info(key); }
+static inline bool ssh_key_check_cert(
+    ssh_key *key, bool host, ptrlen principal, uint64_t time,
+    const ca_options *opts, BinarySink *error)
+{ return key->vt->check_cert(key, host, principal, time, opts, error); }
 static inline int ssh_key_public_bits(const ssh_keyalg *self, ptrlen blob)
 static inline int ssh_key_public_bits(const ssh_keyalg *self, ptrlen blob)
 { return self->pubkey_bits(self, blob); }
 { return self->pubkey_bits(self, blob); }
 static inline const ssh_keyalg *ssh_key_alg(ssh_key *key)
 static inline const ssh_keyalg *ssh_key_alg(ssh_key *key)
@@ -913,6 +969,73 @@ static inline const char *ssh_key_ssh_id(ssh_key *key)
 { return key->vt->ssh_id; }
 { return key->vt->ssh_id; }
 static inline const char *ssh_key_cache_id(ssh_key *key)
 static inline const char *ssh_key_cache_id(ssh_key *key)
 { return key->vt->cache_id; }
 { return key->vt->cache_id; }
+static inline unsigned ssh_key_supported_flags(ssh_key *key)
+{ return key->vt->supported_flags(key->vt); }
+static inline unsigned ssh_keyalg_supported_flags(const ssh_keyalg *self)
+{ return self->supported_flags(self); }
+static inline const char *ssh_keyalg_alternate_ssh_id(
+    const ssh_keyalg *self, unsigned flags)
+{ return self->alternate_ssh_id(self, flags); }
+static inline char *ssh_keyalg_desc(const ssh_keyalg *self)
+{ return self->alg_desc(self); }
+static inline bool ssh_keyalg_variable_size(const ssh_keyalg *self)
+{ return self->variable_size(self); }
+static inline const ssh_keyalg *ssh_keyalg_related_alg(
+    const ssh_keyalg *self, const ssh_keyalg *base)
+{ return self->related_alg(self, base); }
+
+/* Stub functions shared between multiple key types */
+unsigned nullkey_supported_flags(const ssh_keyalg *self);
+const char *nullkey_alternate_ssh_id(const ssh_keyalg *self, unsigned flags);
+ssh_key *nullkey_base_key(ssh_key *key);
+bool nullkey_variable_size_no(const ssh_keyalg *self);
+bool nullkey_variable_size_yes(const ssh_keyalg *self);
+
+/* Utility functions implemented centrally */
+ssh_key *ssh_key_clone(ssh_key *key);
+
+/*
+ * SSH2 ECDH key exchange vtable
+ */
+struct ecdh_key {
+    const ecdh_keyalg *vt;
+};
+struct ecdh_keyalg {
+    /* Unusually, the 'new' method here doesn't directly take a vt
+     * pointer, because it will also need the containing ssh_kex
+     * structure for top-level parameters, and since that contains a
+     * vt pointer anyway, we might as well _only_ pass that. */
+    ecdh_key *(*new)(const ssh_kex *kex, bool is_server);
+    void (*free)(ecdh_key *key);
+    void (*getpublic)(ecdh_key *key, BinarySink *bs);
+    bool (*getkey)(ecdh_key *key, ptrlen remoteKey, BinarySink *bs);
+    char *(*description)(const ssh_kex *kex);
+};
+static inline ecdh_key *ecdh_key_new(const ssh_kex *kex, bool is_server)
+{ return kex->ecdh_vt->new(kex, is_server); }
+static inline void ecdh_key_free(ecdh_key *key)
+{ key->vt->free(key); }
+static inline void ecdh_key_getpublic(ecdh_key *key, BinarySink *bs)
+{ key->vt->getpublic(key, bs); }
+static inline bool ecdh_key_getkey(ecdh_key *key, ptrlen remoteKey,
+                                   BinarySink *bs)
+{ return key->vt->getkey(key, remoteKey, bs); }
+static inline char *ecdh_keyalg_description(const ssh_kex *kex)
+{ return kex->ecdh_vt->description(kex); }
+
+/*
+ * Suffix on GSSAPI SSH protocol identifiers that indicates Kerberos 5
+ * as the mechanism.
+ *
+ * This suffix is the base64-encoded MD5 hash of the byte sequence
+ * 06 09 2A 86 48 86 F7 12 01 02 02, which in turn is the ASN.1 DER
+ * encoding of the object ID 1.2.840.113554.1.2.2 which designates
+ * Kerberos v5.
+ *
+ * (The same encoded OID, minus the two-byte DER header, is defined in
+ * ssh/pgssapi.c as GSS_MECH_KRB5.)
+ */
+#define GSS_KRB5_OID_HASH "toWM5Slw5Ew8Mqkay+al2g=="
 
 
 /*
 /*
  * Enumeration of signature flags from draft-miller-ssh-agent-02
  * Enumeration of signature flags from draft-miller-ssh-agent-02
@@ -999,6 +1122,10 @@ extern const ssh_cipheralg ssh_aes256_sdctr;
 extern const ssh_cipheralg ssh_aes256_sdctr_ni;
 extern const ssh_cipheralg ssh_aes256_sdctr_ni;
 extern const ssh_cipheralg ssh_aes256_sdctr_neon;
 extern const ssh_cipheralg ssh_aes256_sdctr_neon;
 extern const ssh_cipheralg ssh_aes256_sdctr_sw;
 extern const ssh_cipheralg ssh_aes256_sdctr_sw;
+extern const ssh_cipheralg ssh_aes256_gcm;
+extern const ssh_cipheralg ssh_aes256_gcm_ni;
+extern const ssh_cipheralg ssh_aes256_gcm_neon;
+extern const ssh_cipheralg ssh_aes256_gcm_sw;
 extern const ssh_cipheralg ssh_aes256_cbc;
 extern const ssh_cipheralg ssh_aes256_cbc;
 extern const ssh_cipheralg ssh_aes256_cbc_ni;
 extern const ssh_cipheralg ssh_aes256_cbc_ni;
 extern const ssh_cipheralg ssh_aes256_cbc_neon;
 extern const ssh_cipheralg ssh_aes256_cbc_neon;
@@ -1007,6 +1134,10 @@ extern const ssh_cipheralg ssh_aes192_sdctr;
 extern const ssh_cipheralg ssh_aes192_sdctr_ni;
 extern const ssh_cipheralg ssh_aes192_sdctr_ni;
 extern const ssh_cipheralg ssh_aes192_sdctr_neon;
 extern const ssh_cipheralg ssh_aes192_sdctr_neon;
 extern const ssh_cipheralg ssh_aes192_sdctr_sw;
 extern const ssh_cipheralg ssh_aes192_sdctr_sw;
+extern const ssh_cipheralg ssh_aes192_gcm;
+extern const ssh_cipheralg ssh_aes192_gcm_ni;
+extern const ssh_cipheralg ssh_aes192_gcm_neon;
+extern const ssh_cipheralg ssh_aes192_gcm_sw;
 extern const ssh_cipheralg ssh_aes192_cbc;
 extern const ssh_cipheralg ssh_aes192_cbc;
 extern const ssh_cipheralg ssh_aes192_cbc_ni;
 extern const ssh_cipheralg ssh_aes192_cbc_ni;
 extern const ssh_cipheralg ssh_aes192_cbc_neon;
 extern const ssh_cipheralg ssh_aes192_cbc_neon;
@@ -1015,6 +1146,10 @@ extern const ssh_cipheralg ssh_aes128_sdctr;
 extern const ssh_cipheralg ssh_aes128_sdctr_ni;
 extern const ssh_cipheralg ssh_aes128_sdctr_ni;
 extern const ssh_cipheralg ssh_aes128_sdctr_neon;
 extern const ssh_cipheralg ssh_aes128_sdctr_neon;
 extern const ssh_cipheralg ssh_aes128_sdctr_sw;
 extern const ssh_cipheralg ssh_aes128_sdctr_sw;
+extern const ssh_cipheralg ssh_aes128_gcm;
+extern const ssh_cipheralg ssh_aes128_gcm_ni;
+extern const ssh_cipheralg ssh_aes128_gcm_neon;
+extern const ssh_cipheralg ssh_aes128_gcm_sw;
 extern const ssh_cipheralg ssh_aes128_cbc;
 extern const ssh_cipheralg ssh_aes128_cbc;
 extern const ssh_cipheralg ssh_aes128_cbc_ni;
 extern const ssh_cipheralg ssh_aes128_cbc_ni;
 extern const ssh_cipheralg ssh_aes128_cbc_neon;
 extern const ssh_cipheralg ssh_aes128_cbc_neon;
@@ -1030,6 +1165,7 @@ extern const ssh2_ciphers ssh2_aes;
 extern const ssh2_ciphers ssh2_blowfish;
 extern const ssh2_ciphers ssh2_blowfish;
 extern const ssh2_ciphers ssh2_arcfour;
 extern const ssh2_ciphers ssh2_arcfour;
 extern const ssh2_ciphers ssh2_ccp;
 extern const ssh2_ciphers ssh2_ccp;
+extern const ssh2_ciphers ssh2_aesgcm;
 extern const ssh_hashalg ssh_md5;
 extern const ssh_hashalg ssh_md5;
 extern const ssh_hashalg ssh_sha1;
 extern const ssh_hashalg ssh_sha1;
 extern const ssh_hashalg ssh_sha1_ni;
 extern const ssh_hashalg ssh_sha1_ni;
@@ -1053,11 +1189,21 @@ extern const ssh_hashalg ssh_shake256_114bytes;
 extern const ssh_hashalg ssh_blake2b;
 extern const ssh_hashalg ssh_blake2b;
 extern const ssh_kexes ssh_diffiehellman_group1;
 extern const ssh_kexes ssh_diffiehellman_group1;
 extern const ssh_kexes ssh_diffiehellman_group14;
 extern const ssh_kexes ssh_diffiehellman_group14;
+extern const ssh_kexes ssh_diffiehellman_group15;
+extern const ssh_kexes ssh_diffiehellman_group16;
+extern const ssh_kexes ssh_diffiehellman_group17;
+extern const ssh_kexes ssh_diffiehellman_group18;
 extern const ssh_kexes ssh_diffiehellman_gex;
 extern const ssh_kexes ssh_diffiehellman_gex;
 extern const ssh_kex ssh_diffiehellman_group1_sha1;
 extern const ssh_kex ssh_diffiehellman_group1_sha1;
 extern const ssh_kex ssh_diffiehellman_group14_sha256;
 extern const ssh_kex ssh_diffiehellman_group14_sha256;
 extern const ssh_kex ssh_diffiehellman_group14_sha1;
 extern const ssh_kex ssh_diffiehellman_group14_sha1;
+extern const ssh_kex ssh_diffiehellman_group15_sha512;
+extern const ssh_kex ssh_diffiehellman_group16_sha512;
+extern const ssh_kex ssh_diffiehellman_group17_sha512;
+extern const ssh_kex ssh_diffiehellman_group18_sha512;
 extern const ssh_kexes ssh_gssk5_sha1_kex;
 extern const ssh_kexes ssh_gssk5_sha1_kex;
+extern const ssh_kexes ssh_gssk5_sha2_kex;
+extern const ssh_kexes ssh_gssk5_ecdh_kex;
 extern const ssh_kexes ssh_rsa_kex;
 extern const ssh_kexes ssh_rsa_kex;
 extern const ssh_kex ssh_ec_kex_curve25519;
 extern const ssh_kex ssh_ec_kex_curve25519;
 extern const ssh_kex ssh_ec_kex_curve448;
 extern const ssh_kex ssh_ec_kex_curve448;
@@ -1065,6 +1211,7 @@ extern const ssh_kex ssh_ec_kex_nistp256;
 extern const ssh_kex ssh_ec_kex_nistp384;
 extern const ssh_kex ssh_ec_kex_nistp384;
 extern const ssh_kex ssh_ec_kex_nistp521;
 extern const ssh_kex ssh_ec_kex_nistp521;
 extern const ssh_kexes ssh_ecdh_kex;
 extern const ssh_kexes ssh_ecdh_kex;
+extern const ssh_kexes ssh_ntru_hybrid_kex;
 extern const ssh_keyalg ssh_dsa;
 extern const ssh_keyalg ssh_dsa;
 extern const ssh_keyalg ssh_rsa;
 extern const ssh_keyalg ssh_rsa;
 extern const ssh_keyalg ssh_rsa_sha256;
 extern const ssh_keyalg ssh_rsa_sha256;
@@ -1074,6 +1221,14 @@ extern const ssh_keyalg ssh_ecdsa_ed448;
 extern const ssh_keyalg ssh_ecdsa_nistp256;
 extern const ssh_keyalg ssh_ecdsa_nistp256;
 extern const ssh_keyalg ssh_ecdsa_nistp384;
 extern const ssh_keyalg ssh_ecdsa_nistp384;
 extern const ssh_keyalg ssh_ecdsa_nistp521;
 extern const ssh_keyalg ssh_ecdsa_nistp521;
+extern const ssh_keyalg opensshcert_ssh_dsa;
+extern const ssh_keyalg opensshcert_ssh_rsa;
+extern const ssh_keyalg opensshcert_ssh_rsa_sha256;
+extern const ssh_keyalg opensshcert_ssh_rsa_sha512;
+extern const ssh_keyalg opensshcert_ssh_ecdsa_ed25519;
+extern const ssh_keyalg opensshcert_ssh_ecdsa_nistp256;
+extern const ssh_keyalg opensshcert_ssh_ecdsa_nistp384;
+extern const ssh_keyalg opensshcert_ssh_ecdsa_nistp521;
 extern const ssh2_macalg ssh_hmac_md5;
 extern const ssh2_macalg ssh_hmac_md5;
 extern const ssh2_macalg ssh_hmac_sha1;
 extern const ssh2_macalg ssh_hmac_sha1;
 extern const ssh2_macalg ssh_hmac_sha1_buggy;
 extern const ssh2_macalg ssh_hmac_sha1_buggy;
@@ -1081,12 +1236,20 @@ extern const ssh2_macalg ssh_hmac_sha1_96;
 extern const ssh2_macalg ssh_hmac_sha1_96_buggy;
 extern const ssh2_macalg ssh_hmac_sha1_96_buggy;
 extern const ssh2_macalg ssh_hmac_sha256;
 extern const ssh2_macalg ssh_hmac_sha256;
 extern const ssh2_macalg ssh2_poly1305;
 extern const ssh2_macalg ssh2_poly1305;
+extern const ssh2_macalg ssh2_aesgcm_mac;
+extern const ssh2_macalg ssh2_aesgcm_mac_sw;
+extern const ssh2_macalg ssh2_aesgcm_mac_ref_poly;
+extern const ssh2_macalg ssh2_aesgcm_mac_clmul;
+extern const ssh2_macalg ssh2_aesgcm_mac_neon;
 extern const ssh_compression_alg ssh_zlib;
 extern const ssh_compression_alg ssh_zlib;
 
 
 /* Special constructor: BLAKE2b can be instantiated with any hash
 /* Special constructor: BLAKE2b can be instantiated with any hash
  * length up to 128 bytes */
  * length up to 128 bytes */
 ssh_hash *blake2b_new_general(unsigned hashlen);
 ssh_hash *blake2b_new_general(unsigned hashlen);
 
 
+/* Special test function for AES-GCM */
+void aesgcm_set_prefix_lengths(ssh2_mac *mac, size_t skip, size_t aad);
+
 /*
 /*
  * On some systems, you have to detect hardware crypto acceleration by
  * On some systems, you have to detect hardware crypto acceleration by
  * asking the local OS API rather than OS-agnostically asking the CPU
  * asking the local OS API rather than OS-agnostically asking the CPU
@@ -1094,6 +1257,7 @@ ssh_hash *blake2b_new_general(unsigned hashlen);
  * platform subdirectory.
  * platform subdirectory.
  */
  */
 bool platform_aes_neon_available(void);
 bool platform_aes_neon_available(void);
+bool platform_pmull_neon_available(void);
 bool platform_sha256_neon_available(void);
 bool platform_sha256_neon_available(void);
 bool platform_sha1_neon_available(void);
 bool platform_sha1_neon_available(void);
 bool platform_sha512_neon_available(void);
 bool platform_sha512_neon_available(void);
@@ -1270,11 +1434,7 @@ static inline bool is_base64_char(char c)
             c == '+' || c == '/' || c == '=');
             c == '+' || c == '/' || c == '=');
 }
 }
 
 
-extern int base64_decode_atom(const char *atom, unsigned char *out);
 extern int base64_lines(int datalen);
 extern int base64_lines(int datalen);
-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);
 
 
 /* ppk_load_* can return this as an error */
 /* ppk_load_* can return this as an error */
 extern ssh2_userkey ssh2_wrong_passphrase;
 extern ssh2_userkey ssh2_wrong_passphrase;
@@ -1341,6 +1501,9 @@ extern const size_t n_keyalgs;
 const ssh_keyalg *find_pubkey_alg(const char *name);
 const ssh_keyalg *find_pubkey_alg(const char *name);
 const ssh_keyalg *find_pubkey_alg_len(ptrlen name);
 const ssh_keyalg *find_pubkey_alg_len(ptrlen name);
 
 
+ptrlen pubkey_blob_to_alg_name(ptrlen blob);
+const ssh_keyalg *pubkey_blob_to_alg(ptrlen blob);
+
 /* Convenient wrappers on the LoadedFile mechanism suitable for key files */
 /* Convenient wrappers on the LoadedFile mechanism suitable for key files */
 LoadedFile *lf_load_keyfile(const Filename *filename, const char **errptr);
 LoadedFile *lf_load_keyfile(const Filename *filename, const char **errptr);
 LoadedFile *lf_load_keyfile_fp(FILE *fp, const char **errptr);
 LoadedFile *lf_load_keyfile_fp(FILE *fp, const char **errptr);
@@ -1390,12 +1553,36 @@ enum {
 };
 };
 
 
 typedef enum {
 typedef enum {
+    /* Default fingerprint types strip off a certificate to show you
+     * the fingerprint of the underlying public key */
     SSH_FPTYPE_MD5,
     SSH_FPTYPE_MD5,
     SSH_FPTYPE_SHA256,
     SSH_FPTYPE_SHA256,
+    /* Non-default version of each fingerprint type which is 'raw',
+     * giving you the true hash of the public key blob even if it
+     * includes a certificate */
+    SSH_FPTYPE_MD5_CERT,
+    SSH_FPTYPE_SHA256_CERT,
 } FingerprintType;
 } FingerprintType;
 
 
+static inline bool ssh_fptype_is_cert(FingerprintType fptype)
+{
+    return fptype >= SSH_FPTYPE_MD5_CERT;
+}
+static inline FingerprintType ssh_fptype_from_cert(FingerprintType fptype)
+{
+    if (ssh_fptype_is_cert(fptype))
+        fptype -= (SSH_FPTYPE_MD5_CERT - SSH_FPTYPE_MD5);
+    return fptype;
+}
+static inline FingerprintType ssh_fptype_to_cert(FingerprintType fptype)
+{
+    if (!ssh_fptype_is_cert(fptype))
+        fptype += (SSH_FPTYPE_MD5_CERT - SSH_FPTYPE_MD5);
+    return fptype;
+}
+
+#define SSH_N_FPTYPES (SSH_FPTYPE_SHA256_CERT + 1)
 #define SSH_FPTYPE_DEFAULT SSH_FPTYPE_SHA256
 #define SSH_FPTYPE_DEFAULT SSH_FPTYPE_SHA256
-#define SSH_N_FPTYPES (SSH_FPTYPE_SHA256 + 1)
 
 
 FingerprintType ssh2_pick_fingerprint(char **fingerprints,
 FingerprintType ssh2_pick_fingerprint(char **fingerprints,
                                       FingerprintType preferred_type);
                                       FingerprintType preferred_type);
@@ -1409,6 +1596,8 @@ void ssh2_write_pubkey(FILE *fp, const char *comment,
                        int keytype);
                        int keytype);
 char *ssh2_fingerprint_blob(ptrlen, FingerprintType);
 char *ssh2_fingerprint_blob(ptrlen, FingerprintType);
 char *ssh2_fingerprint(ssh_key *key, FingerprintType);
 char *ssh2_fingerprint(ssh_key *key, FingerprintType);
+char *ssh2_double_fingerprint_blob(ptrlen, FingerprintType);
+char *ssh2_double_fingerprint(ssh_key *key, FingerprintType);
 char **ssh2_all_fingerprints_for_blob(ptrlen);
 char **ssh2_all_fingerprints_for_blob(ptrlen);
 char **ssh2_all_fingerprints(ssh_key *key);
 char **ssh2_all_fingerprints(ssh_key *key);
 void ssh2_free_all_fingerprints(char **);
 void ssh2_free_all_fingerprints(char **);
@@ -1423,7 +1612,7 @@ bool import_possible(int type);
 int import_target_type(int type);
 int import_target_type(int type);
 bool import_encrypted(const Filename *filename, int type, char **comment);
 bool import_encrypted(const Filename *filename, int type, char **comment);
 bool import_encrypted_s(const Filename *filename, BinarySource *src,
 bool import_encrypted_s(const Filename *filename, BinarySource *src,
-                      int type, char **comment);
+                        int type, char **comment);
 int import_ssh1(const Filename *filename, int type,
 int import_ssh1(const Filename *filename, int type,
                 RSAKey *key, char *passphrase, const char **errmsg_p);
                 RSAKey *key, char *passphrase, const char **errmsg_p);
 int import_ssh1_s(BinarySource *src, int type,
 int import_ssh1_s(BinarySource *src, int type,
@@ -1431,7 +1620,7 @@ int import_ssh1_s(BinarySource *src, int type,
 ssh2_userkey *import_ssh2(const Filename *filename, int type,
 ssh2_userkey *import_ssh2(const Filename *filename, int type,
                           char *passphrase, const char **errmsg_p);
                           char *passphrase, const char **errmsg_p);
 ssh2_userkey *import_ssh2_s(BinarySource *src, int type,
 ssh2_userkey *import_ssh2_s(BinarySource *src, int type,
-                          char *passphrase, const char **errmsg_p);
+                            char *passphrase, const char **errmsg_p);
 bool export_ssh1(const Filename *filename, int type,
 bool export_ssh1(const Filename *filename, int type,
                  RSAKey *key, char *passphrase);
                  RSAKey *key, char *passphrase);
 bool export_ssh2(const Filename *filename, int type,
 bool export_ssh2(const Filename *filename, int type,
@@ -1728,6 +1917,7 @@ void old_keyfile_warning(void);
     X(BUG_CHOKES_ON_WINADJ)                     \
     X(BUG_CHOKES_ON_WINADJ)                     \
     X(BUG_SENDS_LATE_REQUEST_REPLY)             \
     X(BUG_SENDS_LATE_REQUEST_REPLY)             \
     X(BUG_SSH2_OLDGEX)                          \
     X(BUG_SSH2_OLDGEX)                          \
+    X(BUG_REQUIRES_FILTERED_KEXINIT)            \
     /* end of list */
     /* end of list */
 #define TMP_DECLARE_LOG2_ENUM(thing) log2_##thing,
 #define TMP_DECLARE_LOG2_ENUM(thing) log2_##thing,
 enum { SSH_IMPL_BUG_LIST(TMP_DECLARE_LOG2_ENUM) };
 enum { SSH_IMPL_BUG_LIST(TMP_DECLARE_LOG2_ENUM) };
@@ -1745,13 +1935,14 @@ unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset);
               alloc_channel_id_general(tree, offsetof(type, localid)))
               alloc_channel_id_general(tree, offsetof(type, localid)))
 
 
 void add_to_commasep(strbuf *buf, const char *data);
 void add_to_commasep(strbuf *buf, const char *data);
+void add_to_commasep_pl(strbuf *buf, ptrlen data);
 bool get_commasep_word(ptrlen *list, ptrlen *word);
 bool get_commasep_word(ptrlen *list, ptrlen *word);
 
 
 SeatPromptResult verify_ssh_host_key(
 SeatPromptResult verify_ssh_host_key(
     InteractionReadySeat iseat, Conf *conf, const char *host, int port,
     InteractionReadySeat iseat, Conf *conf, const char *host, int port,
     ssh_key *key, const char *keytype, char *keystr, const char *keydisp,
     ssh_key *key, const char *keytype, char *keystr, const char *keydisp,
-    char **fingerprints, void (*callback)(void *ctx, SeatPromptResult result),
-    void *ctx);
+    char **fingerprints, int ca_count,
+    void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
 
 
 typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache;
 typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache;
 ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void);
 ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void);
@@ -1765,3 +1956,35 @@ bool ssh_transient_hostkey_cache_has(
 bool ssh_transient_hostkey_cache_non_empty(ssh_transient_hostkey_cache *thc);
 bool ssh_transient_hostkey_cache_non_empty(ssh_transient_hostkey_cache *thc);
 
 
 #endif // WINSCP_VS
 #endif // WINSCP_VS
+
+/*
+ * Protocol definitions for authentication helper plugins
+ */
+
+#define AUTHPLUGIN_MSG_NAMES(X)                 \
+    X(PLUGIN_INIT, 1)                           \
+    X(PLUGIN_INIT_RESPONSE, 2)                  \
+    X(PLUGIN_PROTOCOL, 3)                       \
+    X(PLUGIN_PROTOCOL_ACCEPT, 4)                \
+    X(PLUGIN_PROTOCOL_REJECT, 5)                \
+    X(PLUGIN_AUTH_SUCCESS, 6)                   \
+    X(PLUGIN_AUTH_FAILURE, 7)                   \
+    X(PLUGIN_INIT_FAILURE, 8)                   \
+    X(PLUGIN_KI_SERVER_REQUEST, 20)             \
+    X(PLUGIN_KI_SERVER_RESPONSE, 21)            \
+    X(PLUGIN_KI_USER_REQUEST, 22)               \
+    X(PLUGIN_KI_USER_RESPONSE, 23)              \
+    /* end of list */
+
+#define PLUGIN_PROTOCOL_MAX_VERSION 2  /* the highest version we speak */
+
+enum {
+    #define ENUMDECL(name, value) name = value,
+    AUTHPLUGIN_MSG_NAMES(ENUMDECL)
+    #undef ENUMDECL
+
+    /* Error codes internal to this implementation, indicating failure
+     * to receive a meaningful packet at all */
+    PLUGIN_NOTYPE = 256, /* packet too short to have a type */
+    PLUGIN_EOF = 257 /* EOF from auth plugin */
+};

+ 15 - 9
source/putty/ssh/bpp2.c

@@ -74,15 +74,6 @@ BinaryPacketProtocol *ssh2_bpp_new(
 
 
 static void ssh2_bpp_free_outgoing_crypto(struct ssh2_bpp_state *s)
 static void ssh2_bpp_free_outgoing_crypto(struct ssh2_bpp_state *s)
 {
 {
-    /*
-     * We must free the MAC before the cipher, because sometimes the
-     * MAC is not actually separately allocated but just a different
-     * facet of the same object as the cipher, in which case
-     * ssh2_mac_free does nothing and ssh_cipher_free does the actual
-     * freeing. So if we freed the cipher first and then tried to
-     * dereference the MAC's vtable pointer to find out how to free
-     * that too, we'd be accessing freed memory.
-     */
     if (s->out.mac)
     if (s->out.mac)
         ssh2_mac_free(s->out.mac);
         ssh2_mac_free(s->out.mac);
     if (s->out.cipher)
     if (s->out.cipher)
@@ -142,6 +133,12 @@ void ssh2_bpp_new_outgoing_crypto(
     s->out.etm_mode = etm_mode;
     s->out.etm_mode = etm_mode;
     if (mac) {
     if (mac) {
         s->out.mac = ssh2_mac_new(mac, s->out.cipher);
         s->out.mac = ssh2_mac_new(mac, s->out.cipher);
+        /*
+         * Important that mac_setkey comes after cipher_setkey,
+         * because in the case where the MAC makes use of the cipher
+         * (e.g. AES-GCM), it will need the cipher to be keyed
+         * already.
+         */
         ssh2_mac_setkey(s->out.mac, make_ptrlen(mac_key, mac->keylen));
         ssh2_mac_setkey(s->out.mac, make_ptrlen(mac_key, mac->keylen));
 
 
         bpp_logevent("Initialised %s outbound MAC algorithm%s%s",
         bpp_logevent("Initialised %s outbound MAC algorithm%s%s",
@@ -199,6 +196,7 @@ void ssh2_bpp_new_incoming_crypto(
     s->in.etm_mode = etm_mode;
     s->in.etm_mode = etm_mode;
     if (mac) {
     if (mac) {
         s->in.mac = ssh2_mac_new(mac, s->in.cipher);
         s->in.mac = ssh2_mac_new(mac, s->in.cipher);
+        /* MAC setkey has to follow cipher, just as in outgoing_crypto above */
         ssh2_mac_setkey(s->in.mac, make_ptrlen(mac_key, mac->keylen));
         ssh2_mac_setkey(s->in.mac, make_ptrlen(mac_key, mac->keylen));
 
 
         bpp_logevent("Initialised %s inbound MAC algorithm%s%s",
         bpp_logevent("Initialised %s inbound MAC algorithm%s%s",
@@ -523,6 +521,10 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
         dts_consume(&s->stats->in, s->packetlen);
         dts_consume(&s->stats->in, s->packetlen);
 
 
         s->pktin->sequence = s->in.sequence++;
         s->pktin->sequence = s->in.sequence++;
+        if (s->in.cipher)
+            ssh_cipher_next_message(s->in.cipher);
+        if (s->in.mac)
+            ssh2_mac_next_message(s->in.mac);
 
 
         s->length = s->packetlen - s->pad;
         s->length = s->packetlen - s->pad;
         assert(s->length >= 0);
         assert(s->length >= 0);
@@ -829,6 +831,10 @@ static void ssh2_bpp_format_packet_inner(struct ssh2_bpp_state *s, PktOut *pkt)
     }
     }
 
 
     s->out.sequence++;       /* whether or not we MACed */
     s->out.sequence++;       /* whether or not we MACed */
+    if (s->out.cipher)
+        ssh_cipher_next_message(s->out.cipher);
+    if (s->out.mac)
+        ssh2_mac_next_message(s->out.mac);
 
 
     dts_consume(&s->stats->out, origlen + padding);
     dts_consume(&s->stats->out, origlen + padding);
 }
 }

+ 2 - 2
source/putty/ssh/channel.h

@@ -84,10 +84,10 @@ static inline bool chan_want_close(Channel *ch, bool leof, bool reof)
 static inline bool chan_rcvd_exit_status(Channel *ch, int status)
 static inline bool chan_rcvd_exit_status(Channel *ch, int status)
 { return ch->vt->rcvd_exit_status(ch, status); }
 { return ch->vt->rcvd_exit_status(ch, status); }
 static inline bool chan_rcvd_exit_signal(
 static inline bool chan_rcvd_exit_signal(
-        Channel *ch, ptrlen sig, bool core, ptrlen msg)
+    Channel *ch, ptrlen sig, bool core, ptrlen msg)
 { return ch->vt->rcvd_exit_signal(ch, sig, core, msg); }
 { return ch->vt->rcvd_exit_signal(ch, sig, core, msg); }
 static inline bool chan_rcvd_exit_signal_numeric(
 static inline bool chan_rcvd_exit_signal_numeric(
-        Channel *ch, int sig, bool core, ptrlen msg)
+    Channel *ch, int sig, bool core, ptrlen msg)
 { return ch->vt->rcvd_exit_signal_numeric(ch, sig, core, msg); }
 { return ch->vt->rcvd_exit_signal_numeric(ch, sig, core, msg); }
 static inline bool chan_run_shell(Channel *ch)
 static inline bool chan_run_shell(Channel *ch)
 { return ch->vt->run_shell(ch); }
 { return ch->vt->run_shell(ch); }

+ 159 - 11
source/putty/ssh/common_p.c

@@ -632,11 +632,16 @@ unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset)
  * lists of protocol identifiers in SSH-2.
  * lists of protocol identifiers in SSH-2.
  */
  */
 
 
-void add_to_commasep(strbuf *buf, const char *data)
+void add_to_commasep_pl(strbuf *buf, ptrlen data)
 {
 {
     if (buf->len > 0)
     if (buf->len > 0)
         put_byte(buf, ',');
         put_byte(buf, ',');
-    put_data(buf, data, strlen(data));
+    put_datapl(buf, data);
+}
+
+void add_to_commasep(strbuf *buf, const char *data)
+{
+    add_to_commasep_pl(buf, ptrlen_from_asciz(data));
 }
 }
 
 
 bool get_commasep_word(ptrlen *list, ptrlen *word)
 bool get_commasep_word(ptrlen *list, ptrlen *word)
@@ -867,7 +872,7 @@ bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin)
 
 
 #undef BITMAP_UNIVERSAL
 #undef BITMAP_UNIVERSAL
 #undef BITMAP_CONDITIONAL
 #undef BITMAP_CONDITIONAL
-#undef SSH1_BITMAP_WORD
+#undef SSH2_BITMAP_WORD
 
 
 /* ----------------------------------------------------------------------
 /* ----------------------------------------------------------------------
  * Centralised component of SSH host key verification.
  * Centralised component of SSH host key verification.
@@ -882,8 +887,8 @@ bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin)
 SeatPromptResult verify_ssh_host_key(
 SeatPromptResult verify_ssh_host_key(
     InteractionReadySeat iseat, Conf *conf, const char *host, int port,
     InteractionReadySeat iseat, Conf *conf, const char *host, int port,
     ssh_key *key, const char *keytype, char *keystr, const char *keydisp,
     ssh_key *key, const char *keytype, char *keystr, const char *keydisp,
-    char **fingerprints, void (*callback)(void *ctx, SeatPromptResult result),
-    void *ctx)
+    char **fingerprints, int ca_count,
+    void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
 {
 {
     /*
     /*
      * First, check if the Conf includes a manual specification of the
      * First, check if the Conf includes a manual specification of the
@@ -956,12 +961,155 @@ SeatPromptResult verify_ssh_host_key(
      * The key is either missing from the cache, or does not match.
      * The key is either missing from the cache, or does not match.
      * Either way, fall back to an interactive prompt from the Seat.
      * Either way, fall back to an interactive prompt from the Seat.
      */
      */
-    { // WINSCP
-    bool mismatch = (storage_status != 1);
-    return seat_confirm_ssh_host_key(
-        iseat, host, port, keytype, keystr, keydisp, fingerprints, mismatch,
-        callback, ctx);
-    } // WINSCP
+    SeatDialogText *text = seat_dialog_text_new();
+    const SeatDialogPromptDescriptions *pds =
+        seat_prompt_descriptions(iseat.seat);
+
+    FingerprintType fptype_default =
+        ssh2_pick_default_fingerprint(fingerprints);
+
+    seat_dialog_text_append(
+        text, SDT_TITLE, "%s Security Alert", appname);
+
+    HelpCtx helpctx;
+
+    if (key && ssh_key_alg(key)->is_certificate) {
+        seat_dialog_text_append(
+            text, SDT_SCARY_HEADING, "WARNING - POTENTIAL SECURITY BREACH!");
+        seat_dialog_text_append(
+            text, SDT_PARA, "This server presented a certified host key:");
+        seat_dialog_text_append(
+            text, SDT_DISPLAY, "%s (port %d)", host, port);
+        if (ca_count) {
+            seat_dialog_text_append(
+                text, SDT_PARA, "which was signed by a different "
+                "certification authority from the %s %s is configured to "
+                "trust for this server.", ca_count > 1 ? "ones" : "one",
+                appname);
+            if (storage_status == 2) {
+                seat_dialog_text_append(
+                    text, SDT_PARA, "ALSO, that key does not match the key "
+                    "%s had previously cached for this server.", appname);
+                seat_dialog_text_append(
+                    text, SDT_PARA, "This means that either another "
+                    "certification authority is operating in this realm AND "
+                    "the server administrator has changed the host key, or "
+                    "you have actually connected to another computer "
+                    "pretending to be the server.");
+            } else {
+                seat_dialog_text_append(
+                    text, SDT_PARA, "This means that either another "
+                    "certification authority is operating in this realm, or "
+                    "you have actually connected to another computer "
+                    "pretending to be the server.");
+            }
+        } else {
+            assert(storage_status == 2);
+            seat_dialog_text_append(
+                text, SDT_PARA, "which does not match the certified key %s "
+                "had previously cached for this server.", appname);
+            seat_dialog_text_append(
+                text, SDT_PARA, "This means that either the server "
+                "administrator has changed the host key, or you have actually "
+                "connected to another computer pretending to be the server.");
+        }
+        seat_dialog_text_append(
+            text, SDT_PARA, "The new %s key fingerprint is:", keytype);
+        seat_dialog_text_append(
+            text, SDT_DISPLAY, "%s", fingerprints[fptype_default]);
+        helpctx = HELPCTX(errors_cert_mismatch);
+    } else if (storage_status == 1) {
+        seat_dialog_text_append(
+            text, SDT_PARA, "The host key is not cached for this server:");
+        seat_dialog_text_append(
+            text, SDT_DISPLAY, "%s (port %d)", host, port);
+        seat_dialog_text_append(
+            text, SDT_PARA, "You have no guarantee that the server is the "
+            "computer you think it is.");
+        seat_dialog_text_append(
+            text, SDT_PARA, "The server's %s key fingerprint is:", keytype);
+        seat_dialog_text_append(
+            text, SDT_DISPLAY, "%s", fingerprints[fptype_default]);
+        helpctx = HELPCTX(errors_hostkey_absent);
+    } else {
+        seat_dialog_text_append(
+            text, SDT_SCARY_HEADING, "WARNING - POTENTIAL SECURITY BREACH!");
+        seat_dialog_text_append(
+            text, SDT_PARA, "The host key does not match the one %s has "
+            "cached for this server:", appname);
+        seat_dialog_text_append(
+            text, SDT_DISPLAY, "%s (port %d)", host, port);
+        seat_dialog_text_append(
+            text, SDT_PARA, "This means that either the server administrator "
+            "has changed the host key, or you have actually connected to "
+            "another computer pretending to be the server.");
+        seat_dialog_text_append(
+            text, SDT_PARA, "The new %s key fingerprint is:", keytype);
+        seat_dialog_text_append(
+            text, SDT_DISPLAY, "%s", fingerprints[fptype_default]);
+        helpctx = HELPCTX(errors_hostkey_changed);
+    }
+
+    /* The above text is printed even in batch mode. Here's where we stop if
+     * we can't present interactive prompts. */
+    seat_dialog_text_append(
+        text, SDT_BATCH_ABORT, "Connection abandoned.");
+
+    if (storage_status == 1) {
+        seat_dialog_text_append(
+            text, SDT_PARA, "If you trust this host, %s to add the key to "
+            "%s's cache and carry on connecting.",
+            pds->hk_accept_action, appname);
+        seat_dialog_text_append(
+            text, SDT_PARA, "If you want to carry on connecting just once, "
+            "without adding the key to the cache, %s.",
+            pds->hk_connect_once_action);
+        seat_dialog_text_append(
+            text, SDT_PARA, "If you do not trust this host, %s to abandon the "
+            "connection.", pds->hk_cancel_action);
+        seat_dialog_text_append(
+            text, SDT_PROMPT, "Store key in cache?");
+    } else {
+        seat_dialog_text_append(
+            text, SDT_PARA, "If you were expecting this change and trust the "
+            "new key, %s to update %s's cache and carry on connecting.",
+            pds->hk_accept_action, appname);
+        if (key && ssh_key_alg(key)->is_certificate) {
+            seat_dialog_text_append(
+                text, SDT_PARA, "(Storing this certified key in the cache "
+                "will NOT cause its certification authority to be trusted "
+                "for any other key or host.)");
+        }
+        seat_dialog_text_append(
+            text, SDT_PARA, "If you want to carry on connecting but without "
+            "updating the cache, %s.", pds->hk_connect_once_action);
+        seat_dialog_text_append(
+            text, SDT_PARA, "If you want to abandon the connection "
+            "completely, %s to cancel. %s is the ONLY guaranteed safe choice.",
+            pds->hk_cancel_action, pds->hk_cancel_action_Participle);
+        seat_dialog_text_append(
+            text, SDT_PROMPT, "Update cached key?");
+    }
+
+    seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+                            "Full text of host's public key");
+    seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_BLOB, "%s", keydisp);
+
+    if (fingerprints[SSH_FPTYPE_SHA256]) {
+        seat_dialog_text_append(text, SDT_MORE_INFO_KEY, "SHA256 fingerprint");
+        seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s",
+                                fingerprints[SSH_FPTYPE_SHA256]);
+    }
+    if (fingerprints[SSH_FPTYPE_MD5]) {
+        seat_dialog_text_append(text, SDT_MORE_INFO_KEY, "MD5 fingerprint");
+        seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s",
+                                fingerprints[SSH_FPTYPE_MD5]);
+    }
+
+    SeatPromptResult toret = seat_confirm_ssh_host_key(
+        iseat, host, port, keytype, keystr, text, helpctx, callback, ctx);
+    seat_dialog_text_free(text);
+    return toret;
     } // WINSCP
     } // WINSCP
 }
 }
 
 

+ 3 - 3
source/putty/ssh/connection2.c

@@ -1006,7 +1006,7 @@ static void ssh2_connection_process_queue(PacketProtocolLayer *ppl)
      */
      */
     if (ssh2_connection_need_antispoof_prompt(s)) {
     if (ssh2_connection_need_antispoof_prompt(s)) {
         s->antispoof_prompt = ssh_ppl_new_prompts(&s->ppl);
         s->antispoof_prompt = ssh_ppl_new_prompts(&s->ppl);
-        s->antispoof_prompt->to_server = true;
+        s->antispoof_prompt->to_server = false;
         s->antispoof_prompt->from_server = false;
         s->antispoof_prompt->from_server = false;
         s->antispoof_prompt->name = dupstr("Authentication successful");
         s->antispoof_prompt->name = dupstr("Authentication successful");
         add_prompt(
         add_prompt(
@@ -1605,8 +1605,8 @@ static void ssh2_delete_sharing_channel(ConnectionLayer *cl, unsigned localid)
 }
 }
 
 
 static void ssh2_send_packet_from_downstream(
 static void ssh2_send_packet_from_downstream(
-        ConnectionLayer *cl, unsigned id, int type,
-        const void *data, int datalen, const char *additional_log_text)
+    ConnectionLayer *cl, unsigned id, int type,
+    const void *data, int datalen, const char *additional_log_text)
 {
 {
     struct ssh2_connection_state *s =
     struct ssh2_connection_state *s =
         container_of(cl, struct ssh2_connection_state, cl);
         container_of(cl, struct ssh2_connection_state, cl);

+ 1 - 1
source/putty/ssh/gssc.c

@@ -84,7 +84,7 @@ static Ssh_gss_stat ssh_gssapi_acquire_cred(struct ssh_gss_library *lib,
         gssctx->maj_stat =
         gssctx->maj_stat =
             gss->inquire_cred_by_mech(&gssctx->min_stat, cred,
             gss->inquire_cred_by_mech(&gssctx->min_stat, cred,
                                       (gss_OID) GSS_MECH_KRB5,
                                       (gss_OID) GSS_MECH_KRB5,
-                                      GSS_C_NO_NAME,
+                                      NULL,
                                       &time_rec,
                                       &time_rec,
                                       NULL,
                                       NULL,
                                       NULL);
                                       NULL);

+ 216 - 77
source/putty/ssh/kex2-client.c

@@ -156,7 +156,9 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
                 return;
                 return;
             }
             }
         }
         }
-        s->K = dh_find_K(s->dh_ctx, s->f);
+        mp_int *K = dh_find_K(s->dh_ctx, s->f);
+        put_mp_ssh2(s->kex_shared_secret, K);
+        mp_free(K);
 
 
         /* We assume everything from now on will be quick, and it might
         /* We assume everything from now on will be quick, and it might
          * involve user interaction. */
          * involve user interaction. */
@@ -183,23 +185,19 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
             mp_free(s->p); s->p = NULL;
             mp_free(s->p); s->p = NULL;
         }
         }
     } else if (s->kex_alg->main_type == KEXTYPE_ECDH) {
     } else if (s->kex_alg->main_type == KEXTYPE_ECDH) {
-
-        ppl_logevent("Doing ECDH key exchange with curve %s and hash %s",
-                     ssh_ecdhkex_curve_textname(s->kex_alg),
+        char *desc = ecdh_keyalg_description(s->kex_alg);
+        ppl_logevent("Doing %s, using hash %s", desc,
                      ssh_hash_alg(s->exhash)->text_name);
                      ssh_hash_alg(s->exhash)->text_name);
+        sfree(desc);
+
         s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX;
         s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX;
 
 
-        s->ecdh_key = ssh_ecdhkex_newkey(s->kex_alg);
-        if (!s->ecdh_key) {
-            ssh_sw_abort(s->ppl.ssh, "Unable to generate key for ECDH");
-            *aborted = true;
-            return;
-        }
+        s->ecdh_key = ecdh_key_new(s->kex_alg, false);
 
 
         pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_INIT);
         pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_INIT);
         {
         {
             strbuf *pubpoint = strbuf_new();
             strbuf *pubpoint = strbuf_new();
-            ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint));
+            ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint));
             put_stringsb(pktout, pubpoint);
             put_stringsb(pktout, pubpoint);
         }
         }
 
 
@@ -222,7 +220,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
 
 
         {
         {
             strbuf *pubpoint = strbuf_new();
             strbuf *pubpoint = strbuf_new();
-            ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint));
+            ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint));
             put_string(s->exhash, pubpoint->u, pubpoint->len);
             put_string(s->exhash, pubpoint->u, pubpoint->len);
             strbuf_free(pubpoint);
             strbuf_free(pubpoint);
         }
         }
@@ -230,8 +228,9 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
         {
         {
             ptrlen keydata = get_string(pktin);
             ptrlen keydata = get_string(pktin);
             put_stringpl(s->exhash, keydata);
             put_stringpl(s->exhash, keydata);
-            s->K = ssh_ecdhkex_getkey(s->ecdh_key, keydata);
-            if (!get_err(pktin) && !s->K) {
+            bool ok = ecdh_key_getkey(s->ecdh_key, keydata,
+                                      BinarySink_UPCAST(s->kex_shared_secret));
+            if (!get_err(pktin) && !ok) {
                 ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve "
                 ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve "
                                 "point in ECDH reply");
                                 "point in ECDH reply");
                 *aborted = true;
                 *aborted = true;
@@ -246,10 +245,10 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
             return;
             return;
         }
         }
 
 
-        ssh_ecdhkex_freekey(s->ecdh_key);
+        ecdh_key_free(s->ecdh_key);
         s->ecdh_key = NULL;
         s->ecdh_key = NULL;
 #ifndef NO_GSSAPI
 #ifndef NO_GSSAPI
-    } else if (s->kex_alg->main_type == KEXTYPE_GSS) {
+    } else if (kex_is_gss(s->kex_alg)) {
         ptrlen data;
         ptrlen data;
 
 
         s->ppl.bpp->pls->kctx = SSH2_PKTCTX_GSSKEX;
         s->ppl.bpp->pls->kctx = SSH2_PKTCTX_GSSKEX;
@@ -277,14 +276,25 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
         if (s->nbits > s->kex_alg->hash->hlen * 8)
         if (s->nbits > s->kex_alg->hash->hlen * 8)
             s->nbits = s->kex_alg->hash->hlen * 8;
             s->nbits = s->kex_alg->hash->hlen * 8;
 
 
-        if (dh_is_gex(s->kex_alg)) {
+        assert(!s->ecdh_key);
+        assert(!s->dh_ctx);
+
+        if (s->kex_alg->main_type == KEXTYPE_GSS_ECDH) {
+            s->ecdh_key = ecdh_key_new(s->kex_alg, false);
+
+            char *desc = ecdh_keyalg_description(s->kex_alg);
+            ppl_logevent("Doing GSSAPI (with Kerberos V5) %s with hash %s",
+                         desc, ssh_hash_alg(s->exhash)->text_name);
+            sfree(desc);
+        } else if (dh_is_gex(s->kex_alg)) {
             /*
             /*
              * Work out how big a DH group we will need to allow that
              * Work out how big a DH group we will need to allow that
              * much data.
              * much data.
              */
              */
             s->pbits = 512 << ((s->nbits - 1) / 64);
             s->pbits = 512 << ((s->nbits - 1) / 64);
             ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman "
             ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman "
-                         "group exchange, with minimum %d bits", s->pbits);
+                         "group exchange, with minimum %d bits, and hash %s",
+                         s->pbits, ssh_hash_alg(s->exhash)->text_name);
             pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXGSS_GROUPREQ);
             pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXGSS_GROUPREQ);
             put_uint32(pktout, s->pbits); /* min */
             put_uint32(pktout, s->pbits); /* min */
             put_uint32(pktout, s->pbits); /* preferred */
             put_uint32(pktout, s->pbits); /* preferred */
@@ -315,14 +325,19 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
         } else {
         } else {
             s->dh_ctx = dh_setup_group(s->kex_alg);
             s->dh_ctx = dh_setup_group(s->kex_alg);
             ppl_logevent("Using GSSAPI (with Kerberos V5) Diffie-Hellman with"
             ppl_logevent("Using GSSAPI (with Kerberos V5) Diffie-Hellman with"
-                         " standard group \"%s\"", s->kex_alg->groupname);
+                         " standard group \"%s\" and hash %s",
+                         s->kex_alg->groupname,
+                         ssh_hash_alg(s->exhash)->text_name);
         }
         }
 
 
-        ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman key "
-                     "exchange with hash %s", ssh_hash_alg(s->exhash)->text_name);
         /* Now generate e for Diffie-Hellman. */
         /* Now generate e for Diffie-Hellman. */
         seat_set_busy_status(s->ppl.seat, BUSY_CPU);
         seat_set_busy_status(s->ppl.seat, BUSY_CPU);
-        s->e = dh_create_e(s->dh_ctx);
+        if (s->ecdh_key) {
+            s->ebuf = strbuf_new_nm();
+            ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(s->ebuf));
+        } else {
+            s->e = dh_create_e(s->dh_ctx);
+        }
 
 
         if (s->shgss->lib->gsslogmsg)
         if (s->shgss->lib->gsslogmsg)
             ppl_logevent("%s", s->shgss->lib->gsslogmsg);
             ppl_logevent("%s", s->shgss->lib->gsslogmsg);
@@ -386,7 +401,11 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
                 }
                 }
                 put_string(pktout,
                 put_string(pktout,
                            s->gss_sndtok.value, s->gss_sndtok.length);
                            s->gss_sndtok.value, s->gss_sndtok.length);
-                put_mp_ssh2(pktout, s->e);
+                if (s->ecdh_key) {
+                    put_stringpl(pktout, ptrlen_from_strbuf(s->ebuf));
+                } else {
+                    put_mp_ssh2(pktout, s->e);
+                }
                 pq_push(s->ppl.out_pq, pktout);
                 pq_push(s->ppl.out_pq, pktout);
                 s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok);
                 s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok);
                 ppl_logevent("GSSAPI key exchange initialised");
                 ppl_logevent("GSSAPI key exchange initialised");
@@ -413,7 +432,11 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
                 continue;
                 continue;
               case SSH2_MSG_KEXGSS_COMPLETE:
               case SSH2_MSG_KEXGSS_COMPLETE:
                 s->complete_rcvd = true;
                 s->complete_rcvd = true;
-                s->f = get_mp_ssh2(pktin);
+                if (s->ecdh_key) {
+                    s->fbuf = strbuf_dup_nm(get_string(pktin));
+                } else {
+                    s->f = get_mp_ssh2(pktin);
+                }
                 data = get_string(pktin);
                 data = get_string(pktin);
                 s->mic.value = (char *)data.ptr;
                 s->mic.value = (char *)data.ptr;
                 s->mic.length = data.len;
                 s->mic.length = data.len;
@@ -476,7 +499,16 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
                  s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED ||
                  s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED ||
                  !s->complete_rcvd);
                  !s->complete_rcvd);
 
 
-        {
+        if (s->ecdh_key) {
+            bool ok = ecdh_key_getkey(s->ecdh_key, ptrlen_from_strbuf(s->fbuf),
+                                      BinarySink_UPCAST(s->kex_shared_secret));
+            if (!ok) {
+                ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve "
+                                "point in GSSAPI ECDH reply");
+                *aborted = true;
+                return;
+            }
+        } else {
             const char *err = dh_validate_f(s->dh_ctx, s->f);
             const char *err = dh_validate_f(s->dh_ctx, s->f);
             if (err) {
             if (err) {
                 ssh_proto_error(s->ppl.ssh, "GSSAPI reply failed "
                 ssh_proto_error(s->ppl.ssh, "GSSAPI reply failed "
@@ -484,8 +516,10 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
                 *aborted = true;
                 *aborted = true;
                 return;
                 return;
             }
             }
+            mp_int *K = dh_find_K(s->dh_ctx, s->f);
+            put_mp_ssh2(s->kex_shared_secret, K);
+            mp_free(K);
         }
         }
-        s->K = dh_find_K(s->dh_ctx, s->f);
 
 
         /* We assume everything from now on will be quick, and it might
         /* We assume everything from now on will be quick, and it might
          * involve user interaction. */
          * involve user interaction. */
@@ -493,29 +527,42 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
 
 
         if (!s->hkey)
         if (!s->hkey)
             put_stringz(s->exhash, "");
             put_stringz(s->exhash, "");
-        if (dh_is_gex(s->kex_alg)) {
-            /* min,  preferred, max */
-            put_uint32(s->exhash, s->pbits);
-            put_uint32(s->exhash, s->pbits);
-            put_uint32(s->exhash, s->pbits * 2);
 
 
-            put_mp_ssh2(s->exhash, s->p);
-            put_mp_ssh2(s->exhash, s->g);
+        if (s->ecdh_key) {
+            put_stringpl(s->exhash, ptrlen_from_strbuf(s->ebuf));
+            put_stringpl(s->exhash, ptrlen_from_strbuf(s->fbuf));
+        } else {
+            if (dh_is_gex(s->kex_alg)) {
+                /* min, preferred, max */
+                put_uint32(s->exhash, s->pbits);
+                put_uint32(s->exhash, s->pbits);
+                put_uint32(s->exhash, s->pbits * 2);
+
+                put_mp_ssh2(s->exhash, s->p);
+                put_mp_ssh2(s->exhash, s->g);
+            }
+            put_mp_ssh2(s->exhash, s->e);
+            put_mp_ssh2(s->exhash, s->f);
         }
         }
-        put_mp_ssh2(s->exhash, s->e);
-        put_mp_ssh2(s->exhash, s->f);
 
 
         /*
         /*
          * MIC verification is done below, after we compute the hash
          * MIC verification is done below, after we compute the hash
          * used as the MIC input.
          * used as the MIC input.
          */
          */
 
 
-        dh_cleanup(s->dh_ctx);
-        s->dh_ctx = NULL;
-        mp_free(s->f); s->f = NULL;
-        if (dh_is_gex(s->kex_alg)) {
-            mp_free(s->g); s->g = NULL;
-            mp_free(s->p); s->p = NULL;
+        if (s->ecdh_key) {
+            ecdh_key_free(s->ecdh_key);
+            s->ecdh_key = NULL;
+            strbuf_free(s->ebuf); s->ebuf = NULL;
+            strbuf_free(s->fbuf); s->fbuf = NULL;
+        } else {
+            dh_cleanup(s->dh_ctx);
+            s->dh_ctx = NULL;
+            mp_free(s->f); s->f = NULL;
+            if (dh_is_gex(s->kex_alg)) {
+                mp_free(s->g); s->g = NULL;
+                mp_free(s->p); s->p = NULL;
+            }
         }
         }
 #endif
 #endif
     } else {
     } else {
@@ -586,15 +633,21 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
             strbuf *buf, *outstr;
             strbuf *buf, *outstr;
 
 
             mp_int *tmp = mp_random_bits(nbits - 1);
             mp_int *tmp = mp_random_bits(nbits - 1);
-            s->K = mp_power_2(nbits - 1);
-            mp_add_into(s->K, s->K, tmp);
+            mp_int *K = mp_power_2(nbits - 1);
+            mp_add_into(K, K, tmp);
             mp_free(tmp);
             mp_free(tmp);
 
 
             /*
             /*
              * Encode this as an mpint.
              * Encode this as an mpint.
              */
              */
             buf = strbuf_new_nm();
             buf = strbuf_new_nm();
-            put_mp_ssh2(buf, s->K);
+            put_mp_ssh2(buf, K);
+
+            /*
+             * Store a copy as the output shared secret from the kex.
+             */
+            put_mp_ssh2(s->kex_shared_secret, K);
+            mp_free(K);
 
 
             /*
             /*
              * Encrypt it with the given RSA key.
              * Encrypt it with the given RSA key.
@@ -643,7 +696,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
     ssh2transport_finalise_exhash(s);
     ssh2transport_finalise_exhash(s);
 
 
 #ifndef NO_GSSAPI
 #ifndef NO_GSSAPI
-    if (s->kex_alg->main_type == KEXTYPE_GSS) {
+    if (kex_is_gss(s->kex_alg)) {
         Ssh_gss_buf gss_buf;
         Ssh_gss_buf gss_buf;
         SSH_GSS_CLEAR_BUF(&s->gss_buf);
         SSH_GSS_CLEAR_BUF(&s->gss_buf);
 
 
@@ -672,7 +725,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
          * If this the first KEX, save the GSS context for "gssapi-keyex"
          * If this the first KEX, save the GSS context for "gssapi-keyex"
          * authentication.
          * authentication.
          *
          *
-         * http://tools.ietf.org/html/rfc4462#section-4
+         * https://www.rfc-editor.org/rfc/rfc4462#section-4
          *
          *
          * This method may be used only if the initial key exchange was
          * This method may be used only if the initial key exchange was
          * performed using a GSS-API-based key exchange method defined in
          * performed using a GSS-API-based key exchange method defined in
@@ -691,7 +744,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
     s->dh_ctx = NULL;
     s->dh_ctx = NULL;
 
 
     /* In GSS keyex there's no hostkey signature to verify */
     /* In GSS keyex there's no hostkey signature to verify */
-    if (s->kex_alg->main_type != KEXTYPE_GSS) {
+    if (!kex_is_gss(s->kex_alg)) {
         if (!s->hkey) {
         if (!s->hkey) {
             ssh_proto_error(s->ppl.ssh, "Server's host key is invalid");
             ssh_proto_error(s->ppl.ssh, "Server's host key is invalid");
             *aborted = true;
             *aborted = true;
@@ -710,21 +763,21 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
         }
         }
     }
     }
 
 
-    s->keystr = (s->hkey ? ssh_key_cache_str(s->hkey) : NULL);
+    s->keystr = s->hkey ? ssh_key_cache_str(s->hkey) : NULL;
 #ifndef NO_GSSAPI
 #ifndef NO_GSSAPI
     if (s->gss_kex_used) {
     if (s->gss_kex_used) {
         /*
         /*
          * In a GSS-based session, check the host key (if any) against
          * In a GSS-based session, check the host key (if any) against
          * the transient host key cache.
          * the transient host key cache.
          */
          */
-        if (s->kex_alg->main_type == KEXTYPE_GSS) {
+        if (kex_is_gss(s->kex_alg)) {
 
 
             /*
             /*
              * We've just done a GSS key exchange. If it gave us a
              * We've just done a GSS key exchange. If it gave us a
              * host key, store it.
              * host key, store it.
              */
              */
             if (s->hkey) {
             if (s->hkey) {
-                char *fingerprint = ssh2_fingerprint(
+                char *fingerprint = ssh2_double_fingerprint(
                     s->hkey, SSH_FPTYPE_DEFAULT);
                     s->hkey, SSH_FPTYPE_DEFAULT);
                 ppl_logevent("GSS kex provided fallback host key:");
                 ppl_logevent("GSS kex provided fallback host key:");
                 ppl_logevent("%s", fingerprint);
                 ppl_logevent("%s", fingerprint);
@@ -782,9 +835,10 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
              * An exception is if this was the non-GSS key exchange we
              * An exception is if this was the non-GSS key exchange we
              * triggered on purpose to populate the transient cache.
              * triggered on purpose to populate the transient cache.
              */
              */
-            assert(s->hkey);  /* only KEXTYPE_GSS lets this be null */
+            assert(s->hkey);  /* only KEXTYPE_GSS* lets this be null */
             { // WINSCP
             { // WINSCP
-            char *fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT);
+            char *fingerprint = ssh2_double_fingerprint(
+                s->hkey, SSH_FPTYPE_DEFAULT);
 
 
             if (s->need_gss_transient_hostkey) {
             if (s->need_gss_transient_hostkey) {
                 ppl_logevent("Post-GSS rekey provided fallback host key:");
                 ppl_logevent("Post-GSS rekey provided fallback host key:");
@@ -845,54 +899,135 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
                 }
                 }
             }
             }
 
 
+            ssh2_userkey uk; // WINSCP
+            uk.key = s->hkey; // WINSCP
+            uk.comment = NULL; // WINSCP
+            char **fingerprints = ssh2_all_fingerprints(s->hkey);
+
+            FingerprintType fptype_default =
+                ssh2_pick_default_fingerprint(fingerprints);
+            ppl_logevent("Host key fingerprint is:");
+            ppl_logevent("%s", fingerprints[fptype_default]);
+
             /*
             /*
-             * Authenticate remote host: verify host key. (We've already
-             * checked the signature of the exchange hash.)
+             * Authenticate remote host: verify host key, either by
+             * certification or by the local host key cache.
+             *
+             * (We've already checked the signature of the exchange
+             * hash.)
              */
              */
+            if (ssh_key_alg(s->hkey)->is_certificate) {
+                char *base_fp = ssh2_fingerprint(
+                    s->hkey, ssh_fptype_to_cert(fptype_default));
+                ppl_logevent("Host key is a certificate. "
+                             "Hash including certificate:");
+                ppl_logevent("%s", base_fp);
+                sfree(base_fp);
+
+                strbuf *id_string = strbuf_new();
+                StripCtrlChars *id_string_scc = stripctrl_new(
+                    BinarySink_UPCAST(id_string), false, L'\0');
+                ssh_key_cert_id_string(
+                    s->hkey, BinarySink_UPCAST(id_string_scc));
+                stripctrl_free(id_string_scc);
+                ppl_logevent("Certificate ID string is \"%s\"", id_string->s);
+                strbuf_free(id_string);
+
+                strbuf *ca_pub = strbuf_new();
+                ssh_key_ca_public_blob(s->hkey, BinarySink_UPCAST(ca_pub));
+                host_ca hca_search = { .ca_public_key = ca_pub };
+                host_ca *hca_found = find234(s->host_cas, &hca_search, NULL);
+
+                char *ca_fp = ssh2_fingerprint_blob(ptrlen_from_strbuf(ca_pub),
+                                                    fptype_default);
+                ppl_logevent("Fingerprint of certification authority:");
+                ppl_logevent("%s", ca_fp);
+                sfree(ca_fp);
+
+                strbuf_free(ca_pub);
+
+                strbuf *error = strbuf_new();
+                bool cert_ok = false;
+
+                if (!hca_found) {
+                    put_fmt(error, "Certification authority is not trusted");
+                } else {
+                    ppl_logevent("Certification authority matches '%s'",
+                                 hca_found->name);
+                    cert_ok = ssh_key_check_cert(
+                        s->hkey,
+                        true, /* host certificate */
+                        ptrlen_from_asciz(s->savedhost),
+                        time(NULL),
+                        &hca_found->opts,
+                        BinarySink_UPCAST(error));
+                }
+                if (cert_ok) {
+                    strbuf_free(error);
+                    ssh2_free_all_fingerprints(fingerprints);
+                    ppl_logevent("Accepted certificate");
+                    goto host_key_ok;
+                } else {
+                    ppl_logevent("Rejected host key certificate: %s",
+                                 error->s);
+                    strbuf_free(error);
+                    /* now fall through into normal host key checking */
+                }
+            }
+
             {
             {
-                ssh2_userkey uk; // WINSCP
-                uk.key = s->hkey; // WINSCP
-                uk.comment = NULL; // WINSCP
                 { // WINSCP
                 { // WINSCP
                 char *keydisp = ssh2_pubkey_openssh_str(&uk);
                 char *keydisp = ssh2_pubkey_openssh_str(&uk);
-                char **fingerprints = ssh2_all_fingerprints(s->hkey);
 
 
-                FingerprintType fptype_default =
-                    ssh2_pick_default_fingerprint(fingerprints);
-                ppl_logevent("Host key fingerprint is:");
-                ppl_logevent("%s", fingerprints[fptype_default]);
+                int ca_count = ssh_key_alg(s->hkey)->is_certificate ?
+                    count234(s->host_cas) : 0;
 
 
                 s->spr = verify_ssh_host_key(
                 s->spr = verify_ssh_host_key(
                     ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport,
                     ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport,
                     s->hkey, ssh_key_cache_id(s->hkey), s->keystr, keydisp,
                     s->hkey, ssh_key_cache_id(s->hkey), s->keystr, keydisp,
-                    fingerprints, ssh2_transport_dialog_callback, s);
+                    fingerprints, ca_count, ssh2_transport_dialog_callback, s);
 
 
                 ssh2_free_all_fingerprints(fingerprints);
                 ssh2_free_all_fingerprints(fingerprints);
                 sfree(keydisp);
                 sfree(keydisp);
                 } // WINSCP
                 } // WINSCP
-            }
 #ifdef FUZZING
 #ifdef FUZZING
-            s->spr = SPR_OK;
+                s->spr = SPR_OK;
 #endif
 #endif
-            crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
-            if (spr_is_abort(s->spr)) {
-                *aborted = true;
-                ssh_spr_close(s->ppl.ssh, s->spr, "host key verification");
-                return;
+                crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
+                if (spr_is_abort(s->spr)) {
+                    *aborted = true;
+                    ssh_spr_close(s->ppl.ssh, s->spr, "host key verification");
+                    return;
+                }
+
+                if (ssh_key_alg(s->hkey)->is_certificate) {
+                    /*
+                     * Explain what's going on in the Event Log: if we
+                     * got here by way of a certified key whose
+                     * certificate we didn't like, then we should
+                     * explain why we chose to continue with the
+                     * connection anyway!
+                     */
+                    ppl_logevent("Accepting certified host key anyway based "
+                                 "on cache");
+                }
             }
             }
 
 
+          host_key_ok:
+
             /*
             /*
              * Save this host key, to check against the one presented in
              * Save this host key, to check against the one presented in
              * subsequent rekeys.
              * subsequent rekeys.
              */
              */
-            s->hostkey_str = s->keystr;
-            s->keystr = NULL;
+            strbuf_clear(s->hostkeyblob);
+            ssh_key_public_blob(s->hkey, BinarySink_UPCAST(s->hostkeyblob));
         } else if (s->cross_certifying) {
         } else if (s->cross_certifying) {
             assert(s->hkey);
             assert(s->hkey);
             assert(ssh_key_alg(s->hkey) == s->cross_certifying);
             assert(ssh_key_alg(s->hkey) == s->cross_certifying);
 
 
             { // WINSCP
             { // WINSCP
-            char *fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT);
+            char *fingerprint = ssh2_double_fingerprint(
+                s->hkey, SSH_FPTYPE_DEFAULT);
             ppl_logevent("Storing additional host key for this host:");
             ppl_logevent("Storing additional host key for this host:");
             ppl_logevent("%s", fingerprint);
             ppl_logevent("%s", fingerprint);
             sfree(fingerprint);
             sfree(fingerprint);
@@ -903,8 +1038,8 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
              * Don't forget to store the new key as the one we'll be
              * Don't forget to store the new key as the one we'll be
              * re-checking in future normal rekeys.
              * re-checking in future normal rekeys.
              */
              */
-            s->hostkey_str = s->keystr;
-            s->keystr = NULL;
+            strbuf_clear(s->hostkeyblob);
+            ssh_key_public_blob(s->hkey, BinarySink_UPCAST(s->hostkeyblob));
             } // WINSCP
             } // WINSCP
         } else {
         } else {
             /*
             /*
@@ -913,8 +1048,12 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
              * enforce that the key we're seeing this time is identical to
              * enforce that the key we're seeing this time is identical to
              * the one we saw before.
              * the one we saw before.
              */
              */
-            assert(s->keystr);         /* filled in by prior key exchange */
-            if (strcmp(s->hostkey_str, s->keystr)) {
+            strbuf *thisblob = strbuf_new();
+            ssh_key_public_blob(s->hkey, BinarySink_UPCAST(thisblob));
+            bool match = ptrlen_eq_ptrlen(ptrlen_from_strbuf(thisblob),
+                                          ptrlen_from_strbuf(s->hostkeyblob));
+            strbuf_free(thisblob);
+            if (!match) {
 #ifndef FUZZING
 #ifndef FUZZING
                 ssh_sw_abort(s->ppl.ssh,
                 ssh_sw_abort(s->ppl.ssh,
                              "Host key was different in repeat key exchange");
                              "Host key was different in repeat key exchange");

+ 1 - 1
source/putty/ssh/mainchan.c

@@ -351,7 +351,7 @@ static void mainchan_open_failure(Channel *chan, const char *errtext)
 }
 }
 
 
 static size_t mainchan_send(Channel *chan, bool is_stderr,
 static size_t mainchan_send(Channel *chan, bool is_stderr,
-                         const void *data, size_t length)
+                            const void *data, size_t length)
 {
 {
     pinitassert(chan->vt == &mainchan_channelvt);
     pinitassert(chan->vt == &mainchan_channelvt);
     mainchan *mc = container_of(chan, mainchan, chan);
     mainchan *mc = container_of(chan, mainchan, chan);

+ 69 - 39
source/putty/ssh/pgssapi.c

@@ -9,38 +9,63 @@
 
 
 #ifndef NO_LIBDL
 #ifndef NO_LIBDL
 
 
-/* Reserved static storage for GSS_oids.  Comments are quotes from RFC 2744. */
-static const gss_OID_desc oids[] = {
+/* Reserved static storage for GSS_oids.
+ * Constants of the form GSS_C_NT_* are specified by rfc 2744.
+ * Comments are quotes from RFC 2744 itself.
+ *
+ * These may be #defined to complex expressions by the local header
+ * file, if we're including one in static-GSSAPI mode. (For example,
+ * Heimdal defines them to things like
+ * (&__gss_c_nt_user_name_oid_desc).) So we only define them if
+ * needed. */
+
+#ifndef GSS_C_NT_USER_NAME
+static gss_OID_desc oid_GSS_C_NT_USER_NAME = {
     /* The implementation must reserve static storage for a
     /* The implementation must reserve static storage for a
      * gss_OID_desc object containing the value */
      * gss_OID_desc object containing the value */
-    {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01"},
+    10, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01",
     /* corresponding to an object-identifier value of
     /* corresponding to an object-identifier value of
      * {iso(1) member-body(2) United States(840) mit(113554)
      * {iso(1) member-body(2) United States(840) mit(113554)
      * infosys(1) gssapi(2) generic(1) user_name(1)}.  The constant
      * infosys(1) gssapi(2) generic(1) user_name(1)}.  The constant
      * GSS_C_NT_USER_NAME should be initialized to point
      * GSS_C_NT_USER_NAME should be initialized to point
-     * to that gss_OID_desc.
+     * to that gss_OID_desc. */
+};
+const_gss_OID GSS_C_NT_USER_NAME = &oid_GSS_C_NT_USER_NAME;
+#endif
 
 
-     * The implementation must reserve static storage for a
+#ifndef GSS_C_NT_MACHINE_UID_NAME
+static gss_OID_desc oid_GSS_C_NT_MACHINE_UID_NAME = {
+    /* The implementation must reserve static storage for a
      * gss_OID_desc object containing the value */
      * gss_OID_desc object containing the value */
-    {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02"},
+    10, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02",
     /* corresponding to an object-identifier value of
     /* corresponding to an object-identifier value of
      * {iso(1) member-body(2) United States(840) mit(113554)
      * {iso(1) member-body(2) United States(840) mit(113554)
      * infosys(1) gssapi(2) generic(1) machine_uid_name(2)}.
      * infosys(1) gssapi(2) generic(1) machine_uid_name(2)}.
      * The constant GSS_C_NT_MACHINE_UID_NAME should be
      * The constant GSS_C_NT_MACHINE_UID_NAME should be
-     * initialized to point to that gss_OID_desc.
+     * initialized to point to that gss_OID_desc. */
+};
+const_gss_OID GSS_C_NT_MACHINE_UID_NAME = &oid_GSS_C_NT_MACHINE_UID_NAME;
+#endif
 
 
-     * The implementation must reserve static storage for a
+#ifndef GSS_C_NT_STRING_UID_NAME
+static gss_OID_desc oid_GSS_C_NT_STRING_UID_NAME = {
+    /* The implementation must reserve static storage for a
      * gss_OID_desc object containing the value */
      * gss_OID_desc object containing the value */
-    {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03"},
+    10, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03",
     /* corresponding to an object-identifier value of
     /* corresponding to an object-identifier value of
      * {iso(1) member-body(2) United States(840) mit(113554)
      * {iso(1) member-body(2) United States(840) mit(113554)
      * infosys(1) gssapi(2) generic(1) string_uid_name(3)}.
      * infosys(1) gssapi(2) generic(1) string_uid_name(3)}.
      * The constant GSS_C_NT_STRING_UID_NAME should be
      * The constant GSS_C_NT_STRING_UID_NAME should be
-     * initialized to point to that gss_OID_desc.
-     *
-     * The implementation must reserve static storage for a
+     * initialized to point to that gss_OID_desc. */
+};
+const_gss_OID GSS_C_NT_STRING_UID_NAME = &oid_GSS_C_NT_STRING_UID_NAME;
+#endif
+
+#ifndef GSS_C_NT_HOSTBASED_SERVICE_X
+static gss_OID_desc oid_GSS_C_NT_HOSTBASED_SERVICE_X = {
+    /* The implementation must reserve static storage for a
      * gss_OID_desc object containing the value */
      * gss_OID_desc object containing the value */
-    {6, (void *)"\x2b\x06\x01\x05\x06\x02"},
+    6, "\x2b\x06\x01\x05\x06\x02",
     /* corresponding to an object-identifier value of
     /* corresponding to an object-identifier value of
      * {iso(1) org(3) dod(6) internet(1) security(5)
      * {iso(1) org(3) dod(6) internet(1) security(5)
      * nametypes(6) gss-host-based-services(2))}.  The constant
      * nametypes(6) gss-host-based-services(2))}.  The constant
@@ -52,53 +77,58 @@ static const gss_OID_desc oids[] = {
      * GSS_C_NT_HOSTBASED_SERVICE_X should be accepted a synonym
      * GSS_C_NT_HOSTBASED_SERVICE_X should be accepted a synonym
      * for GSS_C_NT_HOSTBASED_SERVICE when presented as an input
      * for GSS_C_NT_HOSTBASED_SERVICE when presented as an input
      * parameter, but should not be emitted by GSS-API
      * parameter, but should not be emitted by GSS-API
-     * implementations
-     *
-     * The implementation must reserve static storage for a
+     * implementations */
+};
+const_gss_OID GSS_C_NT_HOSTBASED_SERVICE_X = &oid_GSS_C_NT_HOSTBASED_SERVICE_X;
+#endif
+
+#ifndef GSS_C_NT_HOSTBASED_SERVICE
+static gss_OID_desc oid_GSS_C_NT_HOSTBASED_SERVICE = {
+    /* The implementation must reserve static storage for a
      * gss_OID_desc object containing the value */
      * gss_OID_desc object containing the value */
-     {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"},
+    10, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04",
     /* corresponding to an object-identifier value of {iso(1)
     /* corresponding to an object-identifier value of {iso(1)
      * member-body(2) Unites States(840) mit(113554) infosys(1)
      * member-body(2) Unites States(840) mit(113554) infosys(1)
      * gssapi(2) generic(1) service_name(4)}.  The constant
      * gssapi(2) generic(1) service_name(4)}.  The constant
      * GSS_C_NT_HOSTBASED_SERVICE should be initialized
      * GSS_C_NT_HOSTBASED_SERVICE should be initialized
-     * to point to that gss_OID_desc.
-     *
-     * The implementation must reserve static storage for a
+     * to point to that gss_OID_desc. */
+};
+const_gss_OID GSS_C_NT_HOSTBASED_SERVICE = &oid_GSS_C_NT_HOSTBASED_SERVICE;
+#endif
+
+#ifndef GSS_C_NT_ANONYMOUS
+static gss_OID_desc oid_GSS_C_NT_ANONYMOUS = {
+    /* The implementation must reserve static storage for a
      * gss_OID_desc object containing the value */
      * gss_OID_desc object containing the value */
-    {6, (void *)"\x2b\x06\01\x05\x06\x03"},
+    6, "\x2b\x06\01\x05\x06\x03",
     /* corresponding to an object identifier value of
     /* corresponding to an object identifier value of
      * {1(iso), 3(org), 6(dod), 1(internet), 5(security),
      * {1(iso), 3(org), 6(dod), 1(internet), 5(security),
      * 6(nametypes), 3(gss-anonymous-name)}.  The constant
      * 6(nametypes), 3(gss-anonymous-name)}.  The constant
      * and GSS_C_NT_ANONYMOUS should be initialized to point
      * and GSS_C_NT_ANONYMOUS should be initialized to point
-     * to that gss_OID_desc.
-     *
-     * The implementation must reserve static storage for a
+     * to that gss_OID_desc. */
+};
+const_gss_OID GSS_C_NT_ANONYMOUS = &oid_GSS_C_NT_ANONYMOUS;
+#endif
+
+#ifndef GSS_C_NT_EXPORT_NAME
+static gss_OID_desc oid_GSS_C_NT_EXPORT_NAME = {
+    /* The implementation must reserve static storage for a
      * gss_OID_desc object containing the value */
      * gss_OID_desc object containing the value */
-    {6, (void *)"\x2b\x06\x01\x05\x06\x04"},
-     /* corresponding to an object-identifier value of
+    6, "\x2b\x06\x01\x05\x06\x04",
+    /* corresponding to an object-identifier value of
      * {1(iso), 3(org), 6(dod), 1(internet), 5(security),
      * {1(iso), 3(org), 6(dod), 1(internet), 5(security),
      * 6(nametypes), 4(gss-api-exported-name)}.  The constant
      * 6(nametypes), 4(gss-api-exported-name)}.  The constant
      * GSS_C_NT_EXPORT_NAME should be initialized to point
      * GSS_C_NT_EXPORT_NAME should be initialized to point
      * to that gss_OID_desc.
      * to that gss_OID_desc.
      */
      */
 };
 };
-
-/* Here are the constants which point to the static structure above.
- *
- * Constants of the form GSS_C_NT_* are specified by rfc 2744.
- */
-const_gss_OID GSS_C_NT_USER_NAME           = oids+0;
-const_gss_OID GSS_C_NT_MACHINE_UID_NAME    = oids+1;
-const_gss_OID GSS_C_NT_STRING_UID_NAME     = oids+2;
-const_gss_OID GSS_C_NT_HOSTBASED_SERVICE_X = oids+3;
-const_gss_OID GSS_C_NT_HOSTBASED_SERVICE   = oids+4;
-const_gss_OID GSS_C_NT_ANONYMOUS           = oids+5;
-const_gss_OID GSS_C_NT_EXPORT_NAME         = oids+6;
+const_gss_OID GSS_C_NT_EXPORT_NAME = &oid_GSS_C_NT_EXPORT_NAME;
+#endif
 
 
 #endif /* NO_LIBDL */
 #endif /* NO_LIBDL */
 
 
 static gss_OID_desc gss_mech_krb5_desc =
 static gss_OID_desc gss_mech_krb5_desc =
-{ 9, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
+{ 9, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
 /* iso(1) member-body(2) United States(840) mit(113554) infosys(1) gssapi(2) krb5(2)*/
 /* iso(1) member-body(2) United States(840) mit(113554) infosys(1) gssapi(2) krb5(2)*/
 const gss_OID GSS_MECH_KRB5 = &gss_mech_krb5_desc;
 const gss_OID GSS_MECH_KRB5 = &gss_mech_krb5_desc;
 
 

+ 3 - 3
source/putty/ssh/pgssapi.h

@@ -53,9 +53,9 @@ typedef struct gss_channel_bindings_struct {
     gss_buffer_desc application_data;
     gss_buffer_desc application_data;
 } *gss_channel_bindings_t;
 } *gss_channel_bindings_t;
 
 
-typedef void * gss_ctx_id_t;
-typedef void * gss_name_t;
-typedef void * gss_cred_id_t;
+typedef void *gss_ctx_id_t;
+typedef void *gss_name_t;
+typedef void *gss_cred_id_t;
 
 
 typedef OM_uint32 gss_qop_t;
 typedef OM_uint32 gss_qop_t;
 typedef int gss_cred_usage_t;
 typedef int gss_cred_usage_t;

+ 1 - 1
source/putty/ssh/portfwd.c

@@ -520,7 +520,7 @@ void portfwd_raw_setup(Channel *pfchan, Socket *s, SshChannel *sc)
 }
 }
 
 
 /*
 /*
- called when someone connects to the local port
+ * called when someone connects to the local port
  */
  */
 
 
 static int pfl_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
 static int pfl_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)

+ 5 - 4
source/putty/ssh/ppl.h

@@ -113,12 +113,13 @@ PacketProtocolLayer *ssh2_transport_new(
     const SshServerConfig *ssc);
     const SshServerConfig *ssc);
 PacketProtocolLayer *ssh2_userauth_new(
 PacketProtocolLayer *ssh2_userauth_new(
     PacketProtocolLayer *successor_layer,
     PacketProtocolLayer *successor_layer,
-    const char *hostname, const char *fullhostname,
-    Filename *keyfile, bool show_banner, bool tryagent, bool notrivialauth,
+    const char *hostname, int port, const char *fullhostname,
+    Filename *keyfile, Filename *detached_cert,
+    bool show_banner, bool tryagent, bool notrivialauth,
     const char *default_username, bool change_username,
     const char *default_username, bool change_username,
-    bool try_ki_auth,
-    bool try_gssapi_auth, bool try_gssapi_kex_auth,
+    bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth,
     bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss,
     bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss,
+    const char *auth_plugin,
     const char * loghost, bool change_password, Seat *seat); // WINSCP
     const char * loghost, bool change_password, Seat *seat); // WINSCP
 PacketProtocolLayer *ssh2_connection_new(
 PacketProtocolLayer *ssh2_connection_new(
     Ssh *ssh, ssh_sharing_state *connshare, bool is_simple,
     Ssh *ssh, ssh_sharing_state *connshare, bool is_simple,

+ 40 - 43
source/putty/ssh/sharing.c

@@ -530,8 +530,8 @@ void sharestate_free(ssh_sharing_state *sharestate)
     sfree(sharestate);
     sfree(sharestate);
 }
 }
 
 
-static struct share_halfchannel *share_add_halfchannel
-    (struct ssh_sharing_connstate *cs, unsigned server_id)
+static struct share_halfchannel *share_add_halfchannel(
+    struct ssh_sharing_connstate *cs, unsigned server_id)
 {
 {
     struct share_halfchannel *hc = snew(struct share_halfchannel);
     struct share_halfchannel *hc = snew(struct share_halfchannel);
     hc->server_id = server_id;
     hc->server_id = server_id;
@@ -544,8 +544,8 @@ static struct share_halfchannel *share_add_halfchannel
     }
     }
 }
 }
 
 
-static struct share_halfchannel *share_find_halfchannel
-    (struct ssh_sharing_connstate *cs, unsigned server_id)
+static struct share_halfchannel *share_find_halfchannel(
+    struct ssh_sharing_connstate *cs, unsigned server_id)
 {
 {
     struct share_halfchannel dummyhc;
     struct share_halfchannel dummyhc;
     dummyhc.server_id = server_id;
     dummyhc.server_id = server_id;
@@ -559,9 +559,9 @@ static void share_remove_halfchannel(struct ssh_sharing_connstate *cs,
     sfree(hc);
     sfree(hc);
 }
 }
 
 
-static struct share_channel *share_add_channel
-    (struct ssh_sharing_connstate *cs, unsigned downstream_id,
-     unsigned upstream_id, unsigned server_id, int state, int maxpkt)
+static struct share_channel *share_add_channel(
+    struct ssh_sharing_connstate *cs, unsigned downstream_id,
+    unsigned upstream_id, unsigned server_id, int state, int maxpkt)
 {
 {
     struct share_channel *chan = snew(struct share_channel);
     struct share_channel *chan = snew(struct share_channel);
     chan->downstream_id = downstream_id;
     chan->downstream_id = downstream_id;
@@ -598,16 +598,16 @@ static void share_channel_set_server_id(struct ssh_sharing_connstate *cs,
     add234(cs->channels_by_server, chan);
     add234(cs->channels_by_server, chan);
 }
 }
 
 
-static struct share_channel *share_find_channel_by_upstream
-    (struct ssh_sharing_connstate *cs, unsigned upstream_id)
+static struct share_channel *share_find_channel_by_upstream(
+    struct ssh_sharing_connstate *cs, unsigned upstream_id)
 {
 {
     struct share_channel dummychan;
     struct share_channel dummychan;
     dummychan.upstream_id = upstream_id;
     dummychan.upstream_id = upstream_id;
     return find234(cs->channels_by_us, &dummychan, NULL);
     return find234(cs->channels_by_us, &dummychan, NULL);
 }
 }
 
 
-static struct share_channel *share_find_channel_by_server
-    (struct ssh_sharing_connstate *cs, unsigned server_id)
+static struct share_channel *share_find_channel_by_server(
+    struct ssh_sharing_connstate *cs, unsigned server_id)
 {
 {
     struct share_channel dummychan;
     struct share_channel dummychan;
     dummychan.server_id = server_id;
     dummychan.server_id = server_id;
@@ -626,9 +626,8 @@ static void share_remove_channel(struct ssh_sharing_connstate *cs,
     sfree(chan);
     sfree(chan);
 }
 }
 
 
-static struct share_xchannel *share_add_xchannel
-    (struct ssh_sharing_connstate *cs,
-     unsigned upstream_id, unsigned server_id)
+static struct share_xchannel *share_add_xchannel(
+    struct ssh_sharing_connstate *cs, unsigned upstream_id, unsigned server_id)
 {
 {
     struct share_xchannel *xc = snew(struct share_xchannel);
     struct share_xchannel *xc = snew(struct share_xchannel);
     xc->upstream_id = upstream_id;
     xc->upstream_id = upstream_id;
@@ -647,16 +646,16 @@ static struct share_xchannel *share_add_xchannel
     return xc;
     return xc;
 }
 }
 
 
-static struct share_xchannel *share_find_xchannel_by_upstream
-    (struct ssh_sharing_connstate *cs, unsigned upstream_id)
+static struct share_xchannel *share_find_xchannel_by_upstream(
+    struct ssh_sharing_connstate *cs, unsigned upstream_id)
 {
 {
     struct share_xchannel dummyxc;
     struct share_xchannel dummyxc;
     dummyxc.upstream_id = upstream_id;
     dummyxc.upstream_id = upstream_id;
     return find234(cs->xchannels_by_us, &dummyxc, NULL);
     return find234(cs->xchannels_by_us, &dummyxc, NULL);
 }
 }
 
 
-static struct share_xchannel *share_find_xchannel_by_server
-    (struct ssh_sharing_connstate *cs, unsigned server_id)
+static struct share_xchannel *share_find_xchannel_by_server(
+    struct ssh_sharing_connstate *cs, unsigned server_id)
 {
 {
     struct share_xchannel dummyxc;
     struct share_xchannel dummyxc;
     dummyxc.server_id = server_id;
     dummyxc.server_id = server_id;
@@ -664,16 +663,15 @@ static struct share_xchannel *share_find_xchannel_by_server
 }
 }
 
 
 static void share_remove_xchannel(struct ssh_sharing_connstate *cs,
 static void share_remove_xchannel(struct ssh_sharing_connstate *cs,
-                                 struct share_xchannel *xc)
+                                  struct share_xchannel *xc)
 {
 {
     del234(cs->xchannels_by_us, xc);
     del234(cs->xchannels_by_us, xc);
     del234(cs->xchannels_by_server, xc);
     del234(cs->xchannels_by_server, xc);
     share_xchannel_free(xc);
     share_xchannel_free(xc);
 }
 }
 
 
-static struct share_forwarding *share_add_forwarding
-    (struct ssh_sharing_connstate *cs,
-     const char *host, int port)
+static struct share_forwarding *share_add_forwarding(
+    struct ssh_sharing_connstate *cs, const char *host, int port)
 {
 {
     struct share_forwarding *fwd = snew(struct share_forwarding);
     struct share_forwarding *fwd = snew(struct share_forwarding);
     fwd->host = dupstr(host);
     fwd->host = dupstr(host);
@@ -687,8 +685,8 @@ static struct share_forwarding *share_add_forwarding
     return fwd;
     return fwd;
 }
 }
 
 
-static struct share_forwarding *share_find_forwarding
-    (struct ssh_sharing_connstate *cs, const char *host, int port)
+static struct share_forwarding *share_find_forwarding(
+    struct ssh_sharing_connstate *cs, const char *host, int port)
 {
 {
     struct share_forwarding dummyfwd, *ret;
     struct share_forwarding dummyfwd, *ret;
     dummyfwd.host = dupstr(host);
     dummyfwd.host = dupstr(host);
@@ -989,8 +987,8 @@ static void share_xchannel_add_message(
     xc->msgtail = msg;
     xc->msgtail = msg;
 }
 }
 
 
-void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs,
-                                 struct share_xchannel *xc)
+static void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs,
+                                        struct share_xchannel *xc)
 {
 {
     /*
     /*
      * Handle queued incoming messages from the server destined for an
      * Handle queued incoming messages from the server destined for an
@@ -1013,10 +1011,10 @@ void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs,
             if (get_bool(src)) {
             if (get_bool(src)) {
                 strbuf *packet = strbuf_new();
                 strbuf *packet = strbuf_new();
                 put_uint32(packet, xc->server_id);
                 put_uint32(packet, xc->server_id);
-                ssh_send_packet_from_downstream
-                    (cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_FAILURE,
-                     packet->s, packet->len,
-                     "downstream refused X channel open");
+                ssh_send_packet_from_downstream(
+                    cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_FAILURE,
+                    packet->s, packet->len,
+                    "downstream refused X channel open");
                 strbuf_free(packet);
                 strbuf_free(packet);
             }
             }
         } else if (msg->type == SSH2_MSG_CHANNEL_CLOSE) {
         } else if (msg->type == SSH2_MSG_CHANNEL_CLOSE) {
@@ -1035,10 +1033,9 @@ void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs,
     }
     }
 }
 }
 
 
-void share_xchannel_confirmation(struct ssh_sharing_connstate *cs,
-                                 struct share_xchannel *xc,
-                                 struct share_channel *chan,
-                                 unsigned downstream_window)
+static void share_xchannel_confirmation(
+    struct ssh_sharing_connstate *cs, struct share_xchannel *xc,
+    struct share_channel *chan, unsigned downstream_window)
 {
 {
     strbuf *packet;
     strbuf *packet;
 
 
@@ -1072,8 +1069,8 @@ void share_xchannel_confirmation(struct ssh_sharing_connstate *cs,
     strbuf_free(packet);
     strbuf_free(packet);
 }
 }
 
 
-void share_xchannel_failure(struct ssh_sharing_connstate *cs,
-                            struct share_xchannel *xc)
+static void share_xchannel_failure(struct ssh_sharing_connstate *cs,
+                                   struct share_xchannel *xc)
 {
 {
     /*
     /*
      * If downstream refuses to open our X channel at all for some
      * If downstream refuses to open our X channel at all for some
@@ -1382,9 +1379,9 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs,
                  * cleaned up if downstream goes away.
                  * cleaned up if downstream goes away.
                  */
                  */
                 pkt[wantreplypos] = 1;
                 pkt[wantreplypos] = 1;
-                ssh_send_packet_from_downstream
-                    (cs->parent->cl, cs->id, type, pkt, pktlen,
-                     orig_wantreply ? NULL : "upstream added want_reply flag");
+                ssh_send_packet_from_downstream(
+                    cs->parent->cl, cs->id, type, pkt, pktlen,
+                    orig_wantreply ? NULL : "upstream added want_reply flag");
                 fwd = share_add_forwarding(cs, host, port);
                 fwd = share_add_forwarding(cs, host, port);
                 ssh_sharing_queue_global_request(cs->parent->cl, cs);
                 ssh_sharing_queue_global_request(cs->parent->cl, cs);
 
 
@@ -1445,9 +1442,9 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs,
                  * deleted even if downstream doesn't want to know.
                  * deleted even if downstream doesn't want to know.
                  */
                  */
                 pkt[wantreplypos] = 1;
                 pkt[wantreplypos] = 1;
-                ssh_send_packet_from_downstream
-                    (cs->parent->cl, cs->id, type, pkt, pktlen,
-                     orig_wantreply ? NULL : "upstream added want_reply flag");
+                ssh_send_packet_from_downstream(
+                    cs->parent->cl, cs->id, type, pkt, pktlen,
+                    orig_wantreply ? NULL : "upstream added want_reply flag");
                 ssh_sharing_queue_global_request(cs->parent->cl, cs);
                 ssh_sharing_queue_global_request(cs->parent->cl, cs);
 
 
                 /*
                 /*
@@ -1996,7 +1993,7 @@ static int share_listen_accepting(Plug *plug,
  * configurations which return the same string from this function will
  * configurations which return the same string from this function will
  * be treated as potentially shareable with each other.
  * be treated as potentially shareable with each other.
  */
  */
-char *ssh_share_sockname(const char *host, int port, Conf *conf)
+static char *ssh_share_sockname(const char *host, int port, Conf *conf)
 {
 {
     char *username = NULL;
     char *username = NULL;
     char *sockname;
     char *sockname;

+ 7 - 4
source/putty/ssh/ssh.c

@@ -254,8 +254,10 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv,
                 char *username = get_remote_username(ssh->conf);
                 char *username = get_remote_username(ssh->conf);
 
 
                 userauth_layer = ssh2_userauth_new(
                 userauth_layer = ssh2_userauth_new(
-                    connection_layer, ssh->savedhost, ssh->fullhostname,
+                    connection_layer, ssh->savedhost, ssh->savedport,
+                    ssh->fullhostname,
                     conf_get_filename(ssh->conf, CONF_keyfile),
                     conf_get_filename(ssh->conf, CONF_keyfile),
+                    conf_get_filename(ssh->conf, CONF_detached_cert),
                     conf_get_bool(ssh->conf, CONF_ssh_show_banner),
                     conf_get_bool(ssh->conf, CONF_ssh_show_banner),
                     conf_get_bool(ssh->conf, CONF_tryagent),
                     conf_get_bool(ssh->conf, CONF_tryagent),
                     conf_get_bool(ssh->conf, CONF_ssh_no_trivial_userauth),
                     conf_get_bool(ssh->conf, CONF_ssh_no_trivial_userauth),
@@ -266,14 +268,15 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv,
                     conf_get_bool(ssh->conf, CONF_try_gssapi_auth),
                     conf_get_bool(ssh->conf, CONF_try_gssapi_auth),
                     conf_get_bool(ssh->conf, CONF_try_gssapi_kex),
                     conf_get_bool(ssh->conf, CONF_try_gssapi_kex),
                     conf_get_bool(ssh->conf, CONF_gssapifwd),
                     conf_get_bool(ssh->conf, CONF_gssapifwd),
-                    &ssh->gss_state
+                    &ssh->gss_state,
 #else
 #else
                     false,
                     false,
                     false,
                     false,
                     false,
                     false,
-                    NULL
+                    NULL,
 #endif
 #endif
-                    ,conf_get_str(ssh->conf, CONF_loghost),
+                    conf_get_str(ssh->conf, CONF_auth_plugin),
+                    conf_get_str(ssh->conf, CONF_loghost),
                     conf_get_bool(ssh->conf, CONF_change_password), // WINSCP
                     conf_get_bool(ssh->conf, CONF_change_password), // WINSCP
                     ssh->seat
                     ssh->seat
                     );
                     );

+ 338 - 92
source/putty/ssh/transport2.c

@@ -119,6 +119,7 @@ static const char *const kexlist_descr[NKEXLIST] = {
 };
 };
 
 
 static int weak_algorithm_compare(void *av, void *bv);
 static int weak_algorithm_compare(void *av, void *bv);
+static int ca_blob_compare(void *av, void *bv);
 
 
 PacketProtocolLayer *ssh2_transport_new(
 PacketProtocolLayer *ssh2_transport_new(
     Conf *conf, const char *host, int port, const char *fullhostname,
     Conf *conf, const char *host, int port, const char *fullhostname,
@@ -140,6 +141,7 @@ PacketProtocolLayer *ssh2_transport_new(
     s->server_greeting = dupstr(server_greeting);
     s->server_greeting = dupstr(server_greeting);
     s->stats = stats;
     s->stats = stats;
     s->hostkeyblob = strbuf_new();
     s->hostkeyblob = strbuf_new();
+    s->host_cas = newtree234(ca_blob_compare);
 
 
     pq_in_init(&s->pq_in_higher, higher_layer->seat); // WINSCP
     pq_in_init(&s->pq_in_higher, higher_layer->seat); // WINSCP
     pq_out_init(&s->pq_out_higher, higher_layer->seat); // WINSCP
     pq_out_init(&s->pq_out_higher, higher_layer->seat); // WINSCP
@@ -217,16 +219,25 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl)
     sfree(s->client_greeting);
     sfree(s->client_greeting);
     sfree(s->server_greeting);
     sfree(s->server_greeting);
     sfree(s->keystr);
     sfree(s->keystr);
-    sfree(s->hostkey_str);
     strbuf_free(s->hostkeyblob);
     strbuf_free(s->hostkeyblob);
+    {
+        host_ca *hca;
+        while ( (hca = delpos234(s->host_cas, 0)) )
+            host_ca_free(hca);
+        freetree234(s->host_cas);
+    }
     if (s->hkey && !s->hostkeys) {
     if (s->hkey && !s->hostkeys) {
         ssh_key_free(s->hkey);
         ssh_key_free(s->hkey);
         s->hkey = NULL;
         s->hkey = NULL;
     }
     }
+    for (size_t i = 0; i < NKEXLIST; i++)
+        sfree(s->kexlists[i].algs);
     if (s->f) mp_free(s->f);
     if (s->f) mp_free(s->f);
     if (s->p) mp_free(s->p);
     if (s->p) mp_free(s->p);
     if (s->g) mp_free(s->g);
     if (s->g) mp_free(s->g);
-    if (s->K) mp_free(s->K);
+    if (s->ebuf) strbuf_free(s->ebuf);
+    if (s->fbuf) strbuf_free(s->fbuf);
+    if (s->kex_shared_secret) strbuf_free(s->kex_shared_secret);
     if (s->dh_ctx)
     if (s->dh_ctx)
         dh_cleanup(s->dh_ctx);
         dh_cleanup(s->dh_ctx);
     if (s->rsa_kex_key_needs_freeing) {
     if (s->rsa_kex_key_needs_freeing) {
@@ -234,7 +245,7 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl)
         sfree(s->rsa_kex_key);
         sfree(s->rsa_kex_key);
     }
     }
     if (s->ecdh_key)
     if (s->ecdh_key)
-        ssh_ecdhkex_freekey(s->ecdh_key);
+        ecdh_key_free(s->ecdh_key);
     if (s->exhash)
     if (s->exhash)
         ssh_hash_free(s->exhash);
         ssh_hash_free(s->exhash);
     strbuf_free(s->outgoing_kexinit);
     strbuf_free(s->outgoing_kexinit);
@@ -252,7 +263,7 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl)
  */
  */
 static void ssh2_mkkey(
 static void ssh2_mkkey(
     struct ssh2_transport_state *s, strbuf *out,
     struct ssh2_transport_state *s, strbuf *out,
-    mp_int *K, unsigned char *H, char chr, int keylen)
+    strbuf *kex_shared_secret, unsigned char *H, char chr, int keylen)
 {
 {
     int hlen = s->kex_alg->hash->hlen;
     int hlen = s->kex_alg->hash->hlen;
     int keylen_padded;
     int keylen_padded;
@@ -280,7 +291,7 @@ static void ssh2_mkkey(
     /* First hlen bytes. */
     /* First hlen bytes. */
     h = ssh_hash_new(s->kex_alg->hash);
     h = ssh_hash_new(s->kex_alg->hash);
     if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY))
     if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY))
-        put_mp_ssh2(h, K);
+        put_datapl(h, ptrlen_from_strbuf(kex_shared_secret));
     put_data(h, H, hlen);
     put_data(h, H, hlen);
     put_byte(h, chr);
     put_byte(h, chr);
     put_data(h, s->session_id, s->session_id_len);
     put_data(h, s->session_id, s->session_id_len);
@@ -292,7 +303,7 @@ static void ssh2_mkkey(
 
 
         ssh_hash_reset(h);
         ssh_hash_reset(h);
         if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY))
         if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY))
-            put_mp_ssh2(h, K);
+            put_datapl(h, ptrlen_from_strbuf(kex_shared_secret));
         put_data(h, H, hlen);
         put_data(h, H, hlen);
 
 
         for (offset = hlen; offset < keylen_padded; offset += hlen) {
         for (offset = hlen; offset < keylen_padded; offset += hlen) {
@@ -309,22 +320,27 @@ static void ssh2_mkkey(
  * Find a slot in a KEXINIT algorithm list to use for a new algorithm.
  * Find a slot in a KEXINIT algorithm list to use for a new algorithm.
  * If the algorithm is already in the list, return a pointer to its
  * If the algorithm is already in the list, return a pointer to its
  * entry, otherwise return an entry from the end of the list.
  * entry, otherwise return an entry from the end of the list.
- * This assumes that every time a particular name is passed in, it
- * comes from the same string constant.  If this isn't true, this
- * function may need to be rewritten to use strcmp() instead.
+ *
+ * 'name' is expected to be a ptrlen which it's safe to keep a copy
+ * of.
  */
  */
-static struct kexinit_algorithm *ssh2_kexinit_addalg(struct kexinit_algorithm
-                                                     *list, const char *name)
+static struct kexinit_algorithm *ssh2_kexinit_addalg_pl(
+    struct kexinit_algorithm_list *list, ptrlen name)
 {
 {
-    int i;
-
-    for (i = 0; i < MAXKEXLIST; i++)
-        if (list[i].name == NULL || list[i].name == name) {
-            list[i].name = name;
-            return &list[i];
-        }
+    for (size_t i = 0; i < list->nalgs; i++)
+        if (ptrlen_eq_ptrlen(list->algs[i].name, name))
+            return &list->algs[i];
+
+    sgrowarray(list->algs, list->algsize, list->nalgs);
+    struct kexinit_algorithm *entry = &list->algs[list->nalgs++];
+    entry->name = name;
+    return entry;
+}
 
 
-    unreachable("Should never run out of space in KEXINIT list");
+static struct kexinit_algorithm *ssh2_kexinit_addalg(
+    struct kexinit_algorithm_list *list, const char *name)
+{
+    return ssh2_kexinit_addalg_pl(list, ptrlen_from_asciz(name));
 }
 }
 
 
 bool ssh2_common_filter_queue(PacketProtocolLayer *ppl)
 bool ssh2_common_filter_queue(PacketProtocolLayer *ppl)
@@ -491,10 +507,10 @@ PktIn *ssh2_transport_pop(struct ssh2_transport_state *s)
 
 
 static void ssh2_write_kexinit_lists(
 static void ssh2_write_kexinit_lists(
     /*WINSCP*/ Seat * seat, BinarySink *pktout,
     /*WINSCP*/ Seat * seat, BinarySink *pktout,
-    struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST],
+    struct kexinit_algorithm_list kexlists[NKEXLIST],
     Conf *conf, const SshServerConfig *ssc, int remote_bugs,
     Conf *conf, const SshServerConfig *ssc, int remote_bugs,
     const char *hk_host, int hk_port, const ssh_keyalg *hk_prev,
     const char *hk_host, int hk_port, const ssh_keyalg *hk_prev,
-    ssh_transient_hostkey_cache *thc,
+    ssh_transient_hostkey_cache *thc, tree234 *host_cas,
     ssh_key *const *our_hostkeys, int our_nhostkeys,
     ssh_key *const *our_hostkeys, int our_nhostkeys,
     bool first_time, bool can_gssapi_keyex, bool transient_hostkey_mode)
     bool first_time, bool can_gssapi_keyex, bool transient_hostkey_mode)
 {
 {
@@ -502,7 +518,7 @@ static void ssh2_write_kexinit_lists(
     bool warn;
     bool warn;
 
 
     int n_preferred_kex;
     int n_preferred_kex;
-    const ssh_kexes *preferred_kex[KEX_MAX + 1]; /* +1 for GSSAPI */
+    const ssh_kexes *preferred_kex[KEX_MAX + 3]; /* +3 for GSSAPI */
     int n_preferred_hk;
     int n_preferred_hk;
     int preferred_hk[HK_MAX];
     int preferred_hk[HK_MAX];
     int n_preferred_ciphers;
     int n_preferred_ciphers;
@@ -517,14 +533,33 @@ static void ssh2_write_kexinit_lists(
      * Set up the preferred key exchange. (NULL => warn below here)
      * Set up the preferred key exchange. (NULL => warn below here)
      */
      */
     n_preferred_kex = 0;
     n_preferred_kex = 0;
-    if (can_gssapi_keyex)
+    if (can_gssapi_keyex) {
+        preferred_kex[n_preferred_kex++] = &ssh_gssk5_ecdh_kex;
+        preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha2_kex;
         preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha1_kex;
         preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha1_kex;
+    }
     for (i = 0; i < KEX_MAX; i++) {
     for (i = 0; i < KEX_MAX; i++) {
         switch (conf_get_int_int(conf, CONF_ssh_kexlist, i)) {
         switch (conf_get_int_int(conf, CONF_ssh_kexlist, i)) {
           case KEX_DHGEX:
           case KEX_DHGEX:
             preferred_kex[n_preferred_kex++] =
             preferred_kex[n_preferred_kex++] =
                 &ssh_diffiehellman_gex;
                 &ssh_diffiehellman_gex;
             break;
             break;
+          case KEX_DHGROUP18:
+            preferred_kex[n_preferred_kex++] =
+                &ssh_diffiehellman_group18;
+            break;
+          case KEX_DHGROUP17:
+            preferred_kex[n_preferred_kex++] =
+                &ssh_diffiehellman_group17;
+            break;
+          case KEX_DHGROUP16:
+            preferred_kex[n_preferred_kex++] =
+                &ssh_diffiehellman_group16;
+            break;
+          case KEX_DHGROUP15:
+            preferred_kex[n_preferred_kex++] =
+                &ssh_diffiehellman_group15;
+            break;
           case KEX_DHGROUP14:
           case KEX_DHGROUP14:
             preferred_kex[n_preferred_kex++] =
             preferred_kex[n_preferred_kex++] =
                 &ssh_diffiehellman_group14;
                 &ssh_diffiehellman_group14;
@@ -541,6 +576,10 @@ static void ssh2_write_kexinit_lists(
             preferred_kex[n_preferred_kex++] =
             preferred_kex[n_preferred_kex++] =
                 &ssh_ecdh_kex;
                 &ssh_ecdh_kex;
             break;
             break;
+          case KEX_NTRU_HYBRID:
+            preferred_kex[n_preferred_kex++] =
+                &ssh_ntru_hybrid_kex;
+            break;
           case KEX_WARN:
           case KEX_WARN:
             /* Flag for later. Don't bother if it's the last in
             /* Flag for later. Don't bother if it's the last in
              * the list. */
              * the list. */
@@ -590,6 +629,9 @@ static void ssh2_write_kexinit_lists(
           case CIPHER_CHACHA20:
           case CIPHER_CHACHA20:
             preferred_ciphers[n_preferred_ciphers++] = &ssh2_ccp;
             preferred_ciphers[n_preferred_ciphers++] = &ssh2_ccp;
             break;
             break;
+          case CIPHER_AESGCM:
+            preferred_ciphers[n_preferred_ciphers++] = &ssh2_aesgcm;
+            break;
           case CIPHER_WARN:
           case CIPHER_WARN:
             /* Flag for later. Don't bother if it's the last in
             /* Flag for later. Don't bother if it's the last in
              * the list. */
              * the list. */
@@ -609,15 +651,14 @@ static void ssh2_write_kexinit_lists(
         preferred_comp = &ssh_comp_none;
         preferred_comp = &ssh_comp_none;
 
 
     for (i = 0; i < NKEXLIST; i++)
     for (i = 0; i < NKEXLIST; i++)
-        for (j = 0; j < MAXKEXLIST; j++)
-            kexlists[i][j].name = NULL;
+        kexlists[i].nalgs = 0;
     /* List key exchange algorithms. */
     /* List key exchange algorithms. */
     warn = false;
     warn = false;
     for (i = 0; i < n_preferred_kex; i++) {
     for (i = 0; i < n_preferred_kex; i++) {
         const ssh_kexes *k = preferred_kex[i];
         const ssh_kexes *k = preferred_kex[i];
         if (!k) warn = true;
         if (!k) warn = true;
         else for (j = 0; j < k->nkexes; j++) {
         else for (j = 0; j < k->nkexes; j++) {
-                alg = ssh2_kexinit_addalg(kexlists[KEXLIST_KEX],
+                alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_KEX],
                                           k->list[j]->name);
                                           k->list[j]->name);
                 alg->u.kex.kex = k->list[j];
                 alg->u.kex.kex = k->list[j];
                 alg->u.kex.warn = warn;
                 alg->u.kex.warn = warn;
@@ -632,23 +673,27 @@ static void ssh2_write_kexinit_lists(
         for (i = 0; i < our_nhostkeys; i++) {
         for (i = 0; i < our_nhostkeys; i++) {
             const ssh_keyalg *keyalg = ssh_key_alg(our_hostkeys[i]);
             const ssh_keyalg *keyalg = ssh_key_alg(our_hostkeys[i]);
 
 
-            alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY],
+            alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY],
                                       keyalg->ssh_id);
                                       keyalg->ssh_id);
             alg->u.hk.hostkey = keyalg;
             alg->u.hk.hostkey = keyalg;
             alg->u.hk.hkflags = 0;
             alg->u.hk.hkflags = 0;
             alg->u.hk.warn = false;
             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;
+            uint32_t supported_flags = ssh_keyalg_supported_flags(keyalg);
+            static const uint32_t try_flags[] = {
+                SSH_AGENT_RSA_SHA2_256,
+                SSH_AGENT_RSA_SHA2_512,
+            };
+            for (size_t i = 0; i < lenof(try_flags); i++) {
+                if (try_flags[i] & ~supported_flags)
+                    continue;          /* these flags not supported */
+
+                alg = ssh2_kexinit_addalg(
+                    &kexlists[KEXLIST_HOSTKEY],
+                    ssh_keyalg_alternate_ssh_id(keyalg, try_flags[i]));
 
 
-                alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY],
-                                          "rsa-sha2-512");
                 alg->u.hk.hostkey = keyalg;
                 alg->u.hk.hostkey = keyalg;
-                alg->u.hk.hkflags = SSH_AGENT_RSA_SHA2_512;
+                alg->u.hk.hkflags = try_flags[i];
                 alg->u.hk.warn = false;
                 alg->u.hk.warn = false;
             }
             }
         }
         }
@@ -666,33 +711,91 @@ static void ssh2_write_kexinit_lists(
          * they surely _do_ want to be alerted that a server
          * they surely _do_ want to be alerted that a server
          * they're actually connecting to is using it.
          * they're actually connecting to is using it.
          */
          */
+
+        bool accept_certs = false;
+        {
+            host_ca_enum *handle = enum_host_ca_start();
+            if (handle) {
+                strbuf *name = strbuf_new();
+                while (strbuf_clear(name), enum_host_ca_next(handle, name)) {
+                    host_ca *hca = host_ca_load(name->s);
+                    if (!hca)
+                        continue;
+
+                    if (hca->ca_public_key &&
+                        cert_expr_match_str(hca->validity_expression,
+                                            hk_host, hk_port)) {
+                        accept_certs = true;
+                        add234(host_cas, hca);
+                    } else {
+                        host_ca_free(hca);
+                    }
+                }
+                enum_host_ca_finish(handle);
+                strbuf_free(name);
+            }
+        }
+
+        if (accept_certs) {
+            /* Add all the certificate algorithms first, in preference order */
+            warn = false;
+            for (i = 0; i < n_preferred_hk; i++) {
+                if (preferred_hk[i] == HK_WARN)
+                    warn = true;
+                for (j = 0; j < lenof(ssh2_hostkey_algs); j++) {
+                    const struct ssh_signkey_with_user_pref_id *a =
+                        &ssh2_hostkey_algs[j];
+                    if (!a->alg->is_certificate)
+                        continue;
+                    if (a->id != preferred_hk[i])
+                        continue;
+                    alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY],
+                                              a->alg->ssh_id);
+                    alg->u.hk.hostkey = a->alg;
+                    alg->u.hk.warn = warn;
+                }
+            }
+        }
+
+        /* Next, add algorithms we already know a key for (unless
+         * configured not to do that) */
         warn = false;
         warn = false;
         for (i = 0; i < n_preferred_hk; i++) {
         for (i = 0; i < n_preferred_hk; i++) {
             if (preferred_hk[i] == HK_WARN)
             if (preferred_hk[i] == HK_WARN)
                 warn = true;
                 warn = true;
             for (j = 0; j < lenof(ssh2_hostkey_algs); j++) {
             for (j = 0; j < lenof(ssh2_hostkey_algs); j++) {
-                if (ssh2_hostkey_algs[j].id != preferred_hk[i])
+                const struct ssh_signkey_with_user_pref_id *a =
+                    &ssh2_hostkey_algs[j];
+                if (a->alg->is_certificate && accept_certs)
+                    continue;          /* already added this one */
+                if (a->id != preferred_hk[i])
                     continue;
                     continue;
                 if (conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys) &&
                 if (conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys) &&
                     have_ssh_host_key(seat, hk_host, hk_port, // WINSCP
                     have_ssh_host_key(seat, hk_host, hk_port, // WINSCP
-                                      ssh2_hostkey_algs[j].alg->cache_id)) {
-                    alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY],
-                                              ssh2_hostkey_algs[j].alg->ssh_id);
-                    alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg;
+                                      a->alg->cache_id)) {
+                    alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY],
+                                              a->alg->ssh_id);
+                    alg->u.hk.hostkey = a->alg;
                     alg->u.hk.warn = warn;
                     alg->u.hk.warn = warn;
                 }
                 }
             }
             }
         }
         }
+
+        /* And finally, everything else */
         warn = false;
         warn = false;
         for (i = 0; i < n_preferred_hk; i++) {
         for (i = 0; i < n_preferred_hk; i++) {
             if (preferred_hk[i] == HK_WARN)
             if (preferred_hk[i] == HK_WARN)
                 warn = true;
                 warn = true;
             for (j = 0; j < lenof(ssh2_hostkey_algs); j++) {
             for (j = 0; j < lenof(ssh2_hostkey_algs); j++) {
-                if (ssh2_hostkey_algs[j].id != preferred_hk[i])
+                const struct ssh_signkey_with_user_pref_id *a =
+                    &ssh2_hostkey_algs[j];
+                if (a->alg->is_certificate)
+                    continue;
+                if (a->id != preferred_hk[i])
                     continue;
                     continue;
-                alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY],
-                                          ssh2_hostkey_algs[j].alg->ssh_id);
-                alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg;
+                alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY],
+                                          a->alg->ssh_id);
+                alg->u.hk.hostkey = a->alg;
                 alg->u.hk.warn = warn;
                 alg->u.hk.warn = warn;
             }
             }
         }
         }
@@ -719,7 +822,7 @@ static void ssh2_write_kexinit_lists(
                     continue;
                     continue;
                 if (ssh_transient_hostkey_cache_has(
                 if (ssh_transient_hostkey_cache_has(
                         thc, ssh2_hostkey_algs[j].alg)) {
                         thc, ssh2_hostkey_algs[j].alg)) {
-                    alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY],
+                    alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY],
                                               ssh2_hostkey_algs[j].alg->ssh_id);
                                               ssh2_hostkey_algs[j].alg->ssh_id);
                     alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg;
                     alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg;
                     alg->u.hk.warn = warn;
                     alg->u.hk.warn = warn;
@@ -736,19 +839,19 @@ static void ssh2_write_kexinit_lists(
          * reverification.
          * reverification.
          */
          */
         assert(hk_prev);
         assert(hk_prev);
-        alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], hk_prev->ssh_id);
+        alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], hk_prev->ssh_id);
         alg->u.hk.hostkey = hk_prev;
         alg->u.hk.hostkey = hk_prev;
         alg->u.hk.warn = false;
         alg->u.hk.warn = false;
     }
     }
     if (can_gssapi_keyex) {
     if (can_gssapi_keyex) {
-        alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], "null");
+        alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], "null");
         alg->u.hk.hostkey = NULL;
         alg->u.hk.hostkey = NULL;
     }
     }
     /* List encryption algorithms (client->server then server->client). */
     /* List encryption algorithms (client->server then server->client). */
     for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) {
     for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) {
         warn = false;
         warn = false;
 #ifdef FUZZING
 #ifdef FUZZING
-        alg = ssh2_kexinit_addalg(kexlists[k], "none");
+        alg = ssh2_kexinit_addalg(&kexlists[K], "none");
         alg->u.cipher.cipher = NULL;
         alg->u.cipher.cipher = NULL;
         alg->u.cipher.warn = warn;
         alg->u.cipher.warn = warn;
 #endif /* FUZZING */
 #endif /* FUZZING */
@@ -756,7 +859,7 @@ static void ssh2_write_kexinit_lists(
             const ssh2_ciphers *c = preferred_ciphers[i];
             const ssh2_ciphers *c = preferred_ciphers[i];
             if (!c) warn = true;
             if (!c) warn = true;
             else for (j = 0; j < c->nciphers; j++) {
             else for (j = 0; j < c->nciphers; j++) {
-                    alg = ssh2_kexinit_addalg(kexlists[k],
+                    alg = ssh2_kexinit_addalg(&kexlists[k],
                                               c->list[j]->ssh2_id);
                                               c->list[j]->ssh2_id);
                     alg->u.cipher.cipher = c->list[j];
                     alg->u.cipher.cipher = c->list[j];
                     alg->u.cipher.warn = warn;
                     alg->u.cipher.warn = warn;
@@ -783,7 +886,7 @@ static void ssh2_write_kexinit_lists(
         alg->u.mac.etm = false;
         alg->u.mac.etm = false;
 #endif /* FUZZING */
 #endif /* FUZZING */
         for (i = 0; i < nmacs; i++) {
         for (i = 0; i < nmacs; i++) {
-            alg = ssh2_kexinit_addalg(kexlists[j], maclist[i]->name);
+            alg = ssh2_kexinit_addalg(&kexlists[j], maclist[i]->name);
             alg->u.mac.mac = maclist[i];
             alg->u.mac.mac = maclist[i];
             alg->u.mac.etm = false;
             alg->u.mac.etm = false;
         }
         }
@@ -791,7 +894,7 @@ static void ssh2_write_kexinit_lists(
             /* For each MAC, there may also be an ETM version,
             /* For each MAC, there may also be an ETM version,
              * which we list second. */
              * which we list second. */
             if (maclist[i]->etm_name) {
             if (maclist[i]->etm_name) {
-                alg = ssh2_kexinit_addalg(kexlists[j], maclist[i]->etm_name);
+                alg = ssh2_kexinit_addalg(&kexlists[j], maclist[i]->etm_name);
                 alg->u.mac.mac = maclist[i];
                 alg->u.mac.mac = maclist[i];
                 alg->u.mac.etm = true;
                 alg->u.mac.etm = true;
             }
             }
@@ -804,22 +907,22 @@ static void ssh2_write_kexinit_lists(
     for (j = KEXLIST_CSCOMP; j <= KEXLIST_SCCOMP; j++) {
     for (j = KEXLIST_CSCOMP; j <= KEXLIST_SCCOMP; j++) {
         assert(lenof(compressions) > 1);
         assert(lenof(compressions) > 1);
         /* Prefer non-delayed versions */
         /* Prefer non-delayed versions */
-        alg = ssh2_kexinit_addalg(kexlists[j], preferred_comp->name);
+        alg = ssh2_kexinit_addalg(&kexlists[j], preferred_comp->name);
         alg->u.comp.comp = preferred_comp;
         alg->u.comp.comp = preferred_comp;
         alg->u.comp.delayed = false;
         alg->u.comp.delayed = false;
         if (preferred_comp->delayed_name) {
         if (preferred_comp->delayed_name) {
-            alg = ssh2_kexinit_addalg(kexlists[j],
+            alg = ssh2_kexinit_addalg(&kexlists[j],
                                       preferred_comp->delayed_name);
                                       preferred_comp->delayed_name);
             alg->u.comp.comp = preferred_comp;
             alg->u.comp.comp = preferred_comp;
             alg->u.comp.delayed = true;
             alg->u.comp.delayed = true;
         }
         }
         for (i = 0; i < lenof(compressions); i++) {
         for (i = 0; i < lenof(compressions); i++) {
             const ssh_compression_alg *c = compressions[i];
             const ssh_compression_alg *c = compressions[i];
-            alg = ssh2_kexinit_addalg(kexlists[j], c->name);
+            alg = ssh2_kexinit_addalg(&kexlists[j], c->name);
             alg->u.comp.comp = c;
             alg->u.comp.comp = c;
             alg->u.comp.delayed = false;
             alg->u.comp.delayed = false;
             if (c->delayed_name) {
             if (c->delayed_name) {
-                alg = ssh2_kexinit_addalg(kexlists[j], c->delayed_name);
+                alg = ssh2_kexinit_addalg(&kexlists[j], c->delayed_name);
                 alg->u.comp.comp = c;
                 alg->u.comp.comp = c;
                 alg->u.comp.delayed = true;
                 alg->u.comp.delayed = true;
             }
             }
@@ -837,10 +940,8 @@ static void ssh2_write_kexinit_lists(
             put_datapl(list, ssc->kex_override[i]);
             put_datapl(list, ssc->kex_override[i]);
         } else {
         } else {
         #endif
         #endif
-            for (j = 0; j < MAXKEXLIST; j++) {
-                if (kexlists[i][j].name == NULL) break;
-                add_to_commasep(list, kexlists[i][j].name);
-            }
+            for (j = 0; j < kexlists[i].nalgs; j++)
+                add_to_commasep_pl(list, kexlists[i].algs[j].name);
         #ifndef WINSCP
         #ifndef WINSCP
         }
         }
         #endif
         #endif
@@ -858,14 +959,19 @@ static void ssh2_write_kexinit_lists(
     put_stringz(pktout, "");
     put_stringz(pktout, "");
 }
 }
 
 
+struct server_hostkeys {
+    int *indices;
+    size_t n, size;
+};
+
 static bool ssh2_scan_kexinits(
 static bool ssh2_scan_kexinits(
     ptrlen client_kexinit, ptrlen server_kexinit,
     ptrlen client_kexinit, ptrlen server_kexinit,
-    struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST],
+    struct kexinit_algorithm_list kexlists[NKEXLIST],
     const ssh_kex **kex_alg, const ssh_keyalg **hostkey_alg,
     const ssh_kex **kex_alg, const ssh_keyalg **hostkey_alg,
     transport_direction *cs, transport_direction *sc,
     transport_direction *cs, transport_direction *sc,
     bool *warn_kex, bool *warn_hk, bool *warn_cscipher, bool *warn_sccipher,
     bool *warn_kex, bool *warn_hk, bool *warn_cscipher, bool *warn_sccipher,
     Ssh *ssh, bool *ignore_guess_cs_packet, bool *ignore_guess_sc_packet,
     Ssh *ssh, bool *ignore_guess_cs_packet, bool *ignore_guess_sc_packet,
-    int *n_server_hostkeys, int server_hostkeys[MAXKEXLIST], unsigned *hkflags,
+    struct server_hostkeys *server_hostkeys, unsigned *hkflags,
     bool *can_send_ext_info)
     bool *can_send_ext_info)
 {
 {
     BinarySource client[1], server[1];
     BinarySource client[1], server[1];
@@ -930,10 +1036,9 @@ static bool ssh2_scan_kexinits(
       found_match:
       found_match:
 
 
         selected[i] = NULL;
         selected[i] = NULL;
-        for (j = 0; j < MAXKEXLIST; j++) {
-            if (kexlists[i][j].name &&
-                ptrlen_eq_string(found, kexlists[i][j].name)) {
-                selected[i] = &kexlists[i][j];
+        for (j = 0; j < kexlists[i].nalgs; j++) {
+            if (ptrlen_eq_ptrlen(found, kexlists[i].algs[j].name)) {
+                selected[i] = &kexlists[i].algs[j];
                 break;
                 break;
             }
             }
         }
         }
@@ -1089,15 +1194,15 @@ static bool ssh2_scan_kexinits(
          * one or not. We return these as a list of indices into the
          * one or not. We return these as a list of indices into the
          * constant ssh2_hostkey_algs[] array.
          * constant ssh2_hostkey_algs[] array.
          */
          */
-        *n_server_hostkeys = 0;
-
         { // WINSCP
         { // WINSCP
         ptrlen list = slists[KEXLIST_HOSTKEY];
         ptrlen list = slists[KEXLIST_HOSTKEY];
         ptrlen word; // WINSCP
         ptrlen word; // WINSCP
         for (; get_commasep_word(&list, &word) ;) {
         for (; get_commasep_word(&list, &word) ;) {
             for (i = 0; i < lenof(ssh2_hostkey_algs); i++)
             for (i = 0; i < lenof(ssh2_hostkey_algs); i++)
                 if (ptrlen_eq_string(word, ssh2_hostkey_algs[i].alg->ssh_id)) {
                 if (ptrlen_eq_string(word, ssh2_hostkey_algs[i].alg->ssh_id)) {
-                    server_hostkeys[(*n_server_hostkeys)++] = i;
+                    sgrowarray(server_hostkeys->indices, server_hostkeys->size,
+                               server_hostkeys->n);
+                    server_hostkeys->indices[server_hostkeys->n++] = i;
                     break;
                     break;
                 }
                 }
         }
         }
@@ -1107,9 +1212,95 @@ static bool ssh2_scan_kexinits(
     return true;
     return true;
 }
 }
 
 
+static inline bool delay_outgoing_kexinit(struct ssh2_transport_state *s)
+{
+    if (!(s->ppl.remote_bugs & BUG_REQUIRES_FILTERED_KEXINIT))
+        return false;   /* bug flag not enabled => no need to delay */
+    if (s->incoming_kexinit->len)
+        return false; /* already got a remote KEXINIT we can filter against */
+    return true;
+}
+
+static void filter_outgoing_kexinit(struct ssh2_transport_state *s)
+{
+    strbuf *pktout = strbuf_new();
+    BinarySource osrc[1], isrc[1];
+    BinarySource_BARE_INIT(
+        osrc, s->outgoing_kexinit->u, s->outgoing_kexinit->len);
+    BinarySource_BARE_INIT(
+        isrc, s->incoming_kexinit->u, s->incoming_kexinit->len);
+
+    /* Skip the packet type bytes from both packets */
+    get_byte(osrc);
+    get_byte(isrc);
+
+    /* Copy our cookie into the real output packet; skip their cookie */
+    put_datapl(pktout, get_data(osrc, 16));
+    get_data(isrc, 16);
+
+    /*
+     * Now we expect NKEXLIST+2 name-lists. We write into the outgoing
+     * packet a subset of our intended outgoing one, containing only
+     * names mentioned in the incoming out.
+     *
+     * NKEXLIST+2 because for this purpose we treat the 'languages'
+     * lists the same as the rest. In the rest of this code base we
+     * ignore those.
+     */
+    strbuf *out = strbuf_new();
+    for (size_t i = 0; i < NKEXLIST+2; i++) {
+        strbuf_clear(out);
+        ptrlen olist = get_string(osrc), ilist = get_string(isrc);
+        for (ptrlen oword; get_commasep_word(&olist, &oword) ;) {
+            ptrlen ilist_copy = ilist;
+            bool add = false;
+            for (ptrlen iword; get_commasep_word(&ilist_copy, &iword) ;) {
+                if (ptrlen_eq_ptrlen(oword, iword)) {
+                    /* Found this word in the incoming list. */
+                    add = true;
+                    break;
+                }
+            }
+
+            if (i == KEXLIST_KEX && ptrlen_eq_string(oword, "ext-info-c")) {
+                /* Special case: this will _never_ match anything from the
+                 * server, and we need it to enable SHA-2 based RSA.
+                 *
+                 * If this ever turns out to confuse any server all by
+                 * itself then I suppose we'll need an even more
+                 * draconian bug flag to exclude that too. (Obv, such
+                 * a server wouldn't be able to speak SHA-2 RSA
+                 * anyway.) */
+                add = true;
+            }
+
+            if (add)
+                add_to_commasep_pl(out, oword);
+        }
+        put_stringpl(pktout, ptrlen_from_strbuf(out));
+    }
+    strbuf_free(out);
+
+    /*
+     * Finally, copy the remaining parts of our intended KEXINIT.
+     */
+    put_bool(pktout, get_bool(osrc));  /* first-kex-packet-follows */
+    put_uint32(pktout, get_uint32(osrc)); /* reserved word */
+
+    /*
+     * Dump this data into s->outgoing_kexinit in place of what we had
+     * there before. We need to remember the KEXINIT we _really_ sent,
+     * not the one we'd have liked to send, since the host key
+     * signature will be validated against the former.
+     */
+    strbuf_shrink_to(s->outgoing_kexinit, 1); /* keep the type byte */
+    put_datapl(s->outgoing_kexinit, ptrlen_from_strbuf(pktout));
+    strbuf_free(pktout);
+}
+
 void ssh2transport_finalise_exhash(struct ssh2_transport_state *s)
 void ssh2transport_finalise_exhash(struct ssh2_transport_state *s)
 {
 {
-    put_mp_ssh2(s->exhash, s->K);
+    put_datapl(s->exhash, ptrlen_from_strbuf(s->kex_shared_secret));
     assert(ssh_hash_alg(s->exhash)->hlen <= sizeof(s->exchange_hash));
     assert(ssh_hash_alg(s->exhash)->hlen <= sizeof(s->exchange_hash));
     ssh_hash_final(s->exhash, s->exchange_hash);
     ssh_hash_final(s->exhash, s->exchange_hash);
     s->exhash = NULL;
     s->exhash = NULL;
@@ -1200,13 +1391,13 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
      * Construct our KEXINIT packet, in a strbuf so we can refer to it
      * Construct our KEXINIT packet, in a strbuf so we can refer to it
      * later.
      * later.
      */
      */
-    strbuf_clear(s->client_kexinit);
+    strbuf_clear(s->outgoing_kexinit);
     put_byte(s->outgoing_kexinit, SSH2_MSG_KEXINIT);
     put_byte(s->outgoing_kexinit, SSH2_MSG_KEXINIT);
     random_read(strbuf_append(s->outgoing_kexinit, 16), 16);
     random_read(strbuf_append(s->outgoing_kexinit, 16), 16);
     ssh2_write_kexinit_lists(
     ssh2_write_kexinit_lists(
         /*WINSCP*/ s->ppl.seat, BinarySink_UPCAST(s->outgoing_kexinit), s->kexlists,
         /*WINSCP*/ s->ppl.seat, BinarySink_UPCAST(s->outgoing_kexinit), s->kexlists,
         s->conf, s->ssc, s->ppl.remote_bugs,
         s->conf, s->ssc, s->ppl.remote_bugs,
-        s->savedhost, s->savedport, s->hostkey_alg, s->thc,
+        s->savedhost, s->savedport, s->hostkey_alg, s->thc, s->host_cas,
         s->hostkeys, s->nhostkeys,
         s->hostkeys, s->nhostkeys,
         !s->got_session_id, s->can_gssapi_keyex,
         !s->got_session_id, s->can_gssapi_keyex,
         s->gss_kex_used && !s->need_gss_transient_hostkey);
         s->gss_kex_used && !s->need_gss_transient_hostkey);
@@ -1215,12 +1406,34 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
     put_uint32(s->outgoing_kexinit, 0);             /* reserved */
     put_uint32(s->outgoing_kexinit, 0);             /* reserved */
 
 
     /*
     /*
-     * Send our KEXINIT.
+     * Send our KEXINIT, most of the time.
+     *
+     * An exception: in BUG_REQUIRES_FILTERED_KEXINIT mode, we have to
+     * have seen at least one KEXINIT from the server first, so that
+     * we can filter our own KEXINIT down to contain only algorithms
+     * the server mentioned.
+     *
+     * But we only need to do this on the _first_ key exchange, when
+     * we've never seen a KEXINIT from the server before. In rekeys,
+     * we still have the server's previous KEXINIT lying around, so we
+     * can filter based on that.
+     *
+     * (And a good thing too, since the way you _initiate_ a rekey is
+     * by sending your KEXINIT, so we'd have no way to prod the server
+     * into sending its first!)
      */
      */
-    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT);
-    put_data(pktout, s->outgoing_kexinit->u + 1,
-             s->outgoing_kexinit->len - 1); /* omit initial packet type byte */
-    pq_push(s->ppl.out_pq, pktout);
+    s->kexinit_delayed = delay_outgoing_kexinit(s);
+    if (!s->kexinit_delayed) {
+        if (s->ppl.remote_bugs & BUG_REQUIRES_FILTERED_KEXINIT) {
+            /* Filter based on the KEXINIT from the previous exchange */
+            filter_outgoing_kexinit(s);
+        }
+
+        pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT);
+        put_data(pktout, s->outgoing_kexinit->u + 1,
+                 s->outgoing_kexinit->len - 1); /* omit type byte */
+        pq_push(s->ppl.out_pq, pktout);
+    }
 
 
     /*
     /*
      * Flag that KEX is in progress.
      * Flag that KEX is in progress.
@@ -1242,21 +1455,35 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
     put_byte(s->incoming_kexinit, SSH2_MSG_KEXINIT);
     put_byte(s->incoming_kexinit, SSH2_MSG_KEXINIT);
     put_data(s->incoming_kexinit, get_ptr(pktin), get_avail(pktin));
     put_data(s->incoming_kexinit, get_ptr(pktin), get_avail(pktin));
 
 
+    /*
+     * If we've delayed sending our KEXINIT so as to filter it down to
+     * only things the server won't choke on, send ours now.
+     */
+    if (s->kexinit_delayed) {
+        filter_outgoing_kexinit(s);
+        pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT);
+        put_data(pktout, s->outgoing_kexinit->u + 1,
+                 s->outgoing_kexinit->len - 1); /* omit type byte */
+        pq_push(s->ppl.out_pq, pktout);
+    }
+
     /*
     /*
      * Work through the two KEXINIT packets in parallel to find the
      * Work through the two KEXINIT packets in parallel to find the
      * selected algorithm identifiers.
      * selected algorithm identifiers.
      */
      */
     {
     {
-        int nhk, hks[MAXKEXLIST], i, j;
+        struct server_hostkeys hks = { NULL, 0, 0 };
 
 
         if (!ssh2_scan_kexinits(
         if (!ssh2_scan_kexinits(
                 ptrlen_from_strbuf(s->client_kexinit),
                 ptrlen_from_strbuf(s->client_kexinit),
                 ptrlen_from_strbuf(s->server_kexinit),
                 ptrlen_from_strbuf(s->server_kexinit),
                 s->kexlists, &s->kex_alg, &s->hostkey_alg, s->cstrans,
                 s->kexlists, &s->kex_alg, &s->hostkey_alg, s->cstrans,
                 s->sctrans, &s->warn_kex, &s->warn_hk, &s->warn_cscipher,
                 s->sctrans, &s->warn_kex, &s->warn_hk, &s->warn_cscipher,
-                &s->warn_sccipher, s->ppl.ssh, NULL, &s->ignorepkt, &nhk, hks,
-                &s->hkflags, &s->can_send_ext_info))
+                &s->warn_sccipher, s->ppl.ssh, NULL, &s->ignorepkt, &hks,
+                &s->hkflags, &s->can_send_ext_info)) {
+            sfree(hks.indices);
             return; /* false means a fatal error function was called */
             return; /* false means a fatal error function was called */
+        }
 
 
         /*
         /*
          * In addition to deciding which host key we're actually going
          * In addition to deciding which host key we're actually going
@@ -1270,14 +1497,17 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
          */
          */
         s->n_uncert_hostkeys = 0;
         s->n_uncert_hostkeys = 0;
 
 
-        for (i = 0; i < nhk; i++) {
-            j = hks[i];
+        for (int i = 0; i < hks.n; i++) {
+            int j = hks.indices[i];
             if (ssh2_hostkey_algs[j].alg != s->hostkey_alg &&
             if (ssh2_hostkey_algs[j].alg != s->hostkey_alg &&
+                ssh2_hostkey_algs[j].alg->cache_id &&
                 !have_ssh_host_key(s->ppl.seat, s->savedhost, s->savedport, // WINSCP
                 !have_ssh_host_key(s->ppl.seat, s->savedhost, s->savedport, // WINSCP
                                    ssh2_hostkey_algs[j].alg->cache_id)) {
                                    ssh2_hostkey_algs[j].alg->cache_id)) {
                 s->uncert_hostkeys[s->n_uncert_hostkeys++] = j;
                 s->uncert_hostkeys[s->n_uncert_hostkeys++] = j;
             }
             }
         }
         }
+
+        sfree(hks.indices);
     }
     }
 
 
     if (s->warn_kex) {
     if (s->warn_kex) {
@@ -1379,6 +1609,9 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
      * Actually perform the key exchange.
      * Actually perform the key exchange.
      */
      */
     s->exhash = ssh_hash_new(s->kex_alg->hash);
     s->exhash = ssh_hash_new(s->kex_alg->hash);
+    if (s->kex_shared_secret)
+        strbuf_free(s->kex_shared_secret);
+    s->kex_shared_secret = strbuf_new_nm();
     put_stringz(s->exhash, s->client_greeting);
     put_stringz(s->exhash, s->client_greeting);
     put_stringz(s->exhash, s->server_greeting);
     put_stringz(s->exhash, s->server_greeting);
     put_string(s->exhash, s->client_kexinit->u, s->client_kexinit->len);
     put_string(s->exhash, s->client_kexinit->u, s->client_kexinit->len);
@@ -1432,14 +1665,14 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
         strbuf *mac_key = strbuf_new_nm();
         strbuf *mac_key = strbuf_new_nm();
 
 
         if (s->out.cipher) {
         if (s->out.cipher) {
-            ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash,
+            ssh2_mkkey(s, cipher_iv, s->kex_shared_secret, s->exchange_hash,
                        'A' + s->out.mkkey_adjust, s->out.cipher->blksize);
                        'A' + s->out.mkkey_adjust, s->out.cipher->blksize);
-            ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash,
+            ssh2_mkkey(s, cipher_key, s->kex_shared_secret, s->exchange_hash,
                        'C' + s->out.mkkey_adjust,
                        'C' + s->out.mkkey_adjust,
                        s->out.cipher->padded_keybytes);
                        s->out.cipher->padded_keybytes);
         }
         }
         if (s->out.mac) {
         if (s->out.mac) {
-            ssh2_mkkey(s, mac_key, s->K, s->exchange_hash,
+            ssh2_mkkey(s, mac_key, s->kex_shared_secret, s->exchange_hash,
                        'E' + s->out.mkkey_adjust, s->out.mac->keylen);
                        'E' + s->out.mkkey_adjust, s->out.mac->keylen);
         }
         }
 
 
@@ -1527,14 +1760,14 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
         strbuf *mac_key = strbuf_new_nm();
         strbuf *mac_key = strbuf_new_nm();
 
 
         if (s->in.cipher) {
         if (s->in.cipher) {
-            ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash,
+            ssh2_mkkey(s, cipher_iv, s->kex_shared_secret, s->exchange_hash,
                        'A' + s->in.mkkey_adjust, s->in.cipher->blksize);
                        'A' + s->in.mkkey_adjust, s->in.cipher->blksize);
-            ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash,
+            ssh2_mkkey(s, cipher_key, s->kex_shared_secret, s->exchange_hash,
                        'C' + s->in.mkkey_adjust,
                        'C' + s->in.mkkey_adjust,
                        s->in.cipher->padded_keybytes);
                        s->in.cipher->padded_keybytes);
         }
         }
         if (s->in.mac) {
         if (s->in.mac) {
-            ssh2_mkkey(s, mac_key, s->K, s->exchange_hash,
+            ssh2_mkkey(s, mac_key, s->kex_shared_secret, s->exchange_hash,
                        'E' + s->in.mkkey_adjust, s->in.mac->keylen);
                        'E' + s->in.mkkey_adjust, s->in.mac->keylen);
         }
         }
 
 
@@ -1552,7 +1785,8 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
     /*
     /*
      * Free shared secret.
      * Free shared secret.
      */
      */
-    mp_free(s->K); s->K = NULL;
+    strbuf_free(s->kex_shared_secret);
+    s->kex_shared_secret = NULL;
 
 
     /*
     /*
      * Update the specials menu to list the remaining uncertified host
      * Update the specials menu to list the remaining uncertified host
@@ -2123,9 +2357,9 @@ static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
     for (i = 0; i < CIPHER_MAX; i++)
     for (i = 0; i < CIPHER_MAX; i++)
         if (conf_get_int_int(s->conf, CONF_ssh_cipherlist, i) !=
         if (conf_get_int_int(s->conf, CONF_ssh_cipherlist, i) !=
             conf_get_int_int(conf, CONF_ssh_cipherlist, i)) {
             conf_get_int_int(conf, CONF_ssh_cipherlist, i)) {
-        rekey_reason = "cipher settings changed";
-        rekey_mandatory = true;
-    }
+            rekey_reason = "cipher settings changed";
+            rekey_mandatory = true;
+        }
     if (conf_get_bool(s->conf, CONF_ssh2_des_cbc) !=
     if (conf_get_bool(s->conf, CONF_ssh2_des_cbc) !=
         conf_get_bool(conf, CONF_ssh2_des_cbc)) {
         conf_get_bool(conf, CONF_ssh2_des_cbc)) {
         rekey_reason = "cipher settings changed";
         rekey_reason = "cipher settings changed";
@@ -2155,6 +2389,18 @@ static int weak_algorithm_compare(void *av, void *bv)
     return a < b ? -1 : a > b ? +1 : 0;
     return a < b ? -1 : a > b ? +1 : 0;
 }
 }
 
 
+static int ca_blob_compare(void *av, void *bv)
+{
+    host_ca *a = (host_ca *)av, *b = (host_ca *)bv;
+    strbuf *apk = a->ca_public_key, *bpk = b->ca_public_key;
+    /* Ordering by public key is arbitrary here, so do whatever is easiest */
+    if (apk->len < bpk->len)
+        return -1;
+    if (apk->len > bpk->len)
+        return +1;
+    return memcmp(apk->u, bpk->u, apk->len);
+}
+
 /*
 /*
  * Wrapper on seat_confirm_weak_crypto_primitive(), which uses the
  * Wrapper on seat_confirm_weak_crypto_primitive(), which uses the
  * tree234 s->weak_algorithms_consented_to to ensure we ask at most
  * tree234 s->weak_algorithms_consented_to to ensure we ask at most

+ 32 - 16
source/putty/ssh/transport2.h

@@ -18,9 +18,8 @@
 #define DH_MIN_SIZE 1024
 #define DH_MIN_SIZE 1024
 #define DH_MAX_SIZE 8192
 #define DH_MAX_SIZE 8192
 
 
-#define MAXKEXLIST 16
 struct kexinit_algorithm {
 struct kexinit_algorithm {
-    const char *name;
+    ptrlen name;
     union {
     union {
         struct {
         struct {
             const ssh_kex *kex;
             const ssh_kex *kex;
@@ -45,18 +44,31 @@ struct kexinit_algorithm {
         } comp;
         } comp;
     } u;
     } u;
 };
 };
+struct kexinit_algorithm_list {
+    struct kexinit_algorithm *algs;
+    size_t nalgs, algsize;
+};
 
 
-#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)             \
+#define HOSTKEY_ALGORITHMS(X)                                   \
+    X(HK_ED25519, ssh_ecdsa_ed25519)                            \
+    X(HK_ED448, ssh_ecdsa_ed448)                                \
+    X(HK_ECDSA, ssh_ecdsa_nistp256)                             \
+    X(HK_ECDSA, ssh_ecdsa_nistp384)                             \
+    X(HK_ECDSA, ssh_ecdsa_nistp521)                             \
     /* Changed order to match WinSCP default preference list for SshHostKeyList() */ \
     /* Changed order to match WinSCP default preference list for SshHostKeyList() */ \
-    X(HK_RSA, ssh_rsa_sha512)                   \
-    X(HK_RSA, ssh_rsa_sha256)                   \
-    X(HK_RSA, ssh_rsa)                          \
+    X(HK_RSA, ssh_rsa_sha512)                                   \
+    X(HK_RSA, ssh_rsa_sha256)                                   \
+    X(HK_RSA, ssh_rsa)                                          \
     X(HK_DSA, ssh_dsa)                          \
     X(HK_DSA, ssh_dsa)                          \
+    X(HK_ED25519, opensshcert_ssh_ecdsa_ed25519)                \
+    /* OpenSSH defines no certified version of Ed448 */         \
+    X(HK_ECDSA, opensshcert_ssh_ecdsa_nistp256)                 \
+    X(HK_ECDSA, opensshcert_ssh_ecdsa_nistp384)                 \
+    X(HK_ECDSA, opensshcert_ssh_ecdsa_nistp521)                 \
+    X(HK_DSA, opensshcert_ssh_dsa)                              \
+    X(HK_RSA, opensshcert_ssh_rsa_sha512)                       \
+    X(HK_RSA, opensshcert_ssh_rsa_sha256)                       \
+    X(HK_RSA, opensshcert_ssh_rsa)                              \
     /* end of list */
     /* end of list */
 #define COUNT_HOSTKEY_ALGORITHM(type, alg) +1
 #define COUNT_HOSTKEY_ALGORITHM(type, alg) +1
 #define N_HOSTKEY_ALGORITHMS (0 HOSTKEY_ALGORITHMS(COUNT_HOSTKEY_ALGORITHM))
 #define N_HOSTKEY_ALGORITHMS (0 HOSTKEY_ALGORITHMS(COUNT_HOSTKEY_ALGORITHM))
@@ -129,7 +141,6 @@ struct ssh2_transport_state {
 
 
     const ssh_kex *kex_alg;
     const ssh_kex *kex_alg;
     const ssh_keyalg *hostkey_alg;
     const ssh_keyalg *hostkey_alg;
-    char *hostkey_str; /* string representation, for easy checking in rekeys */
     unsigned char session_id[MAX_HASH_LEN];
     unsigned char session_id[MAX_HASH_LEN];
     int session_id_len;
     int session_id_len;
     int dh_min_size, dh_max_size;
     int dh_min_size, dh_max_size;
@@ -143,7 +154,7 @@ struct ssh2_transport_state {
 
 
     char *client_greeting, *server_greeting;
     char *client_greeting, *server_greeting;
 
 
-    bool kex_in_progress;
+    bool kex_in_progress, kexinit_delayed;
     unsigned long next_rekey, last_rekey;
     unsigned long next_rekey, last_rekey;
     const char *deferred_rekey_reason;
     const char *deferred_rekey_reason;
     bool higher_layer_ok;
     bool higher_layer_ok;
@@ -166,15 +177,20 @@ struct ssh2_transport_state {
 
 
     bool gss_kex_used;
     bool gss_kex_used;
 
 
+    tree234 *host_cas;
+
     int nbits, pbits;
     int nbits, pbits;
     bool warn_kex, warn_hk, warn_cscipher, warn_sccipher;
     bool warn_kex, warn_hk, warn_cscipher, warn_sccipher;
-    mp_int *p, *g, *e, *f, *K;
+    mp_int *p, *g, *e, *f;
+    strbuf *ebuf, *fbuf;
+    strbuf *kex_shared_secret;
     strbuf *outgoing_kexinit, *incoming_kexinit;
     strbuf *outgoing_kexinit, *incoming_kexinit;
     strbuf *client_kexinit, *server_kexinit; /* aliases to the above */
     strbuf *client_kexinit, *server_kexinit; /* aliases to the above */
     int kex_init_value, kex_reply_value;
     int kex_init_value, kex_reply_value;
     transport_direction in, out, *cstrans, *sctrans;
     transport_direction in, out, *cstrans, *sctrans;
     ptrlen hostkeydata, sigdata;
     ptrlen hostkeydata, sigdata;
-    strbuf *hostkeyblob;
+    strbuf *hostkeyblob; /* used in server to construct host key to
+                          * send to client; in client to check in rekeys */
     char *keystr;
     char *keystr;
     ssh_key *hkey;                     /* actual host key */
     ssh_key *hkey;                     /* actual host key */
     unsigned hkflags;                  /* signing flags, used in server */
     unsigned hkflags;                  /* signing flags, used in server */
@@ -190,7 +206,7 @@ struct ssh2_transport_state {
     SeatPromptResult spr;
     SeatPromptResult spr;
     bool guessok;
     bool guessok;
     bool ignorepkt;
     bool ignorepkt;
-    struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST];
+    struct kexinit_algorithm_list kexlists[NKEXLIST];
 #ifndef NO_GSSAPI
 #ifndef NO_GSSAPI
     Ssh_gss_buf gss_buf;
     Ssh_gss_buf gss_buf;
     Ssh_gss_buf gss_rcvtok, gss_sndtok;
     Ssh_gss_buf gss_rcvtok, gss_sndtok;

文件差异内容过多而无法显示
+ 557 - 216
source/putty/ssh/userauth2-client.c


+ 9 - 3
source/putty/ssh/verstring.c

@@ -43,7 +43,7 @@ static void ssh_verstring_handle_input(BinaryPacketProtocol *bpp);
 static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp);
 static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp);
 static PktOut *ssh_verstring_new_pktout(int type);
 static PktOut *ssh_verstring_new_pktout(int type);
 static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp,
 static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp,
-                                          const char *msg, int category);
+                                           const char *msg, int category);
 
 
 static const BinaryPacketProtocolVtable ssh_verstring_vtable = {
 static const BinaryPacketProtocolVtable ssh_verstring_vtable = {
     // WINSCP
     // WINSCP
@@ -310,8 +310,8 @@ void ssh_verstring_handle_input(BinaryPacketProtocol *bpp)
      * a NUL terminator.
      * a NUL terminator.
      */
      */
     while (s->vstring->len > 0 &&
     while (s->vstring->len > 0 &&
-           (s->vstring->s[s->vstring->len-1] == '\r' ||
-            s->vstring->s[s->vstring->len-1] == '\n'))
+           (s->vstring->s[s->vstring->len-1] == '\015' ||
+            s->vstring->s[s->vstring->len-1] == '\012'))
         strbuf_shrink_by(s->vstring, 1);
         strbuf_shrink_by(s->vstring, 1);
 
 
     bpp_logevent("Remote version: %s", s->vstring->s);
     bpp_logevent("Remote version: %s", s->vstring->s);
@@ -607,6 +607,12 @@ static void ssh_detect_bugs(struct ssh_verstring_state *s)
         bpp_logevent("We believe remote version has SSH-2 "
         bpp_logevent("We believe remote version has SSH-2 "
                      "channel request bug");
                      "channel request bug");
     }
     }
+
+    if (conf_get_int(s->conf, CONF_sshbug_filter_kexinit) == FORCE_ON) {
+        s->remote_bugs |= BUG_REQUIRES_FILTERED_KEXINIT;
+        bpp_logevent("We believe remote version requires us to "
+                     "filter our KEXINIT");
+    }
 }
 }
 
 
 const char *ssh_verstring_get_remote(BinaryPacketProtocol *bpp)
 const char *ssh_verstring_get_remote(BinaryPacketProtocol *bpp)

+ 15 - 16
source/putty/ssh/zlib.c

@@ -54,8 +54,8 @@ struct LZ77InternalContext;
 struct LZ77Context {
 struct LZ77Context {
     struct LZ77InternalContext *ictx;
     struct LZ77InternalContext *ictx;
     void *userdata;
     void *userdata;
-    void (*literal) (struct LZ77Context * ctx, unsigned char c);
-    void (*match) (struct LZ77Context * ctx, int distance, int len);
+    void (*literal) (struct LZ77Context *ctx, unsigned char c);
+    void (*match) (struct LZ77Context *ctx, int distance, int len);
 };
 };
 
 
 /*
 /*
@@ -573,7 +573,7 @@ struct ssh_zlib_compressor {
     ssh_compressor sc;
     ssh_compressor sc;
 };
 };
 
 
-ssh_compressor *zlib_compress_init(void)
+static ssh_compressor *zlib_compress_init(void)
 {
 {
     struct Outbuf *out;
     struct Outbuf *out;
     struct ssh_zlib_compressor *comp = snew(struct ssh_zlib_compressor);
     struct ssh_zlib_compressor *comp = snew(struct ssh_zlib_compressor);
@@ -592,7 +592,7 @@ ssh_compressor *zlib_compress_init(void)
     return &comp->sc;
     return &comp->sc;
 }
 }
 
 
-void zlib_compress_cleanup(ssh_compressor *sc)
+static void zlib_compress_cleanup(ssh_compressor *sc)
 {
 {
     struct ssh_zlib_compressor *comp =
     struct ssh_zlib_compressor *comp =
         container_of(sc, struct ssh_zlib_compressor, sc);
         container_of(sc, struct ssh_zlib_compressor, sc);
@@ -604,10 +604,9 @@ void zlib_compress_cleanup(ssh_compressor *sc)
     sfree(comp);
     sfree(comp);
 }
 }
 
 
-void zlib_compress_block(ssh_compressor *sc,
-                         const unsigned char *block, int len,
-                         unsigned char **outblock, int *outlen,
-                         int minlen)
+static void zlib_compress_block(
+    ssh_compressor *sc, const unsigned char *block, int len,
+    unsigned char **outblock, int *outlen, int minlen)
 {
 {
     struct ssh_zlib_compressor *comp =
     struct ssh_zlib_compressor *comp =
         container_of(sc, struct ssh_zlib_compressor, sc);
         container_of(sc, struct ssh_zlib_compressor, sc);
@@ -904,7 +903,7 @@ struct zlib_decompress_ctx {
     ssh_decompressor dc;
     ssh_decompressor dc;
 };
 };
 
 
-ssh_decompressor *zlib_decompress_init(void)
+static ssh_decompressor *zlib_decompress_init(void)
 {
 {
     struct zlib_decompress_ctx *dctx = snew(struct zlib_decompress_ctx);
     struct zlib_decompress_ctx *dctx = snew(struct zlib_decompress_ctx);
     unsigned char lengths[288];
     unsigned char lengths[288];
@@ -927,7 +926,7 @@ ssh_decompressor *zlib_decompress_init(void)
     return &dctx->dc;
     return &dctx->dc;
 }
 }
 
 
-void zlib_decompress_cleanup(ssh_decompressor *dc)
+static void zlib_decompress_cleanup(ssh_decompressor *dc)
 {
 {
     struct zlib_decompress_ctx *dctx =
     struct zlib_decompress_ctx *dctx =
         container_of(dc, struct zlib_decompress_ctx, dc);
         container_of(dc, struct zlib_decompress_ctx, dc);
@@ -946,7 +945,7 @@ void zlib_decompress_cleanup(ssh_decompressor *dc)
 }
 }
 
 
 static int zlib_huflookup(unsigned long *bitsp, int *nbitsp,
 static int zlib_huflookup(unsigned long *bitsp, int *nbitsp,
-                   struct zlib_table *tab)
+                          struct zlib_table *tab)
 {
 {
     unsigned long bits = *bitsp;
     unsigned long bits = *bitsp;
     int nbits = *nbitsp;
     int nbits = *nbitsp;
@@ -986,9 +985,9 @@ static void zlib_emit_char(struct zlib_decompress_ctx *dctx, int c)
 
 
 #define EATBITS(n) ( dctx->nbits -= (n), dctx->bits >>= (n) )
 #define EATBITS(n) ( dctx->nbits -= (n), dctx->bits >>= (n) )
 
 
-bool zlib_decompress_block(ssh_decompressor *dc,
-                           const unsigned char *block, int len,
-                           unsigned char **outblock, int *outlen)
+static bool zlib_decompress_block(
+    ssh_decompressor *dc, const unsigned char *block, int len,
+    unsigned char **outblock, int *outlen)
 {
 {
     struct zlib_decompress_ctx *dctx =
     struct zlib_decompress_ctx *dctx =
         container_of(dc, struct zlib_decompress_ctx, dc);
         container_of(dc, struct zlib_decompress_ctx, dc);
@@ -1094,7 +1093,7 @@ bool zlib_decompress_block(ssh_decompressor *dc,
             if (dctx->lenptr >= dctx->hlit + dctx->hdist) {
             if (dctx->lenptr >= dctx->hlit + dctx->hdist) {
                 dctx->currlentable = zlib_mktable(dctx->lengths, dctx->hlit);
                 dctx->currlentable = zlib_mktable(dctx->lengths, dctx->hlit);
                 dctx->currdisttable = zlib_mktable(dctx->lengths + dctx->hlit,
                 dctx->currdisttable = zlib_mktable(dctx->lengths + dctx->hlit,
-                                                  dctx->hdist);
+                                                   dctx->hdist);
                 zlib_freetable(&dctx->lenlentable);
                 zlib_freetable(&dctx->lenlentable);
                 dctx->lenlentable = NULL;
                 dctx->lenlentable = NULL;
                 dctx->state = INBLK;
                 dctx->state = INBLK;
@@ -1112,7 +1111,7 @@ bool zlib_decompress_block(ssh_decompressor *dc,
                 dctx->lenextrabits = (code == 16 ? 2 : code == 17 ? 3 : 7);
                 dctx->lenextrabits = (code == 16 ? 2 : code == 17 ? 3 : 7);
                 dctx->lenaddon = (code == 18 ? 11 : 3);
                 dctx->lenaddon = (code == 18 ? 11 : 3);
                 dctx->lenrep = (code == 16 && dctx->lenptr > 0 ?
                 dctx->lenrep = (code == 16 && dctx->lenptr > 0 ?
-                               dctx->lengths[dctx->lenptr - 1] : 0);
+                                dctx->lengths[dctx->lenptr - 1] : 0);
                 dctx->state = TREES_LENREP;
                 dctx->state = TREES_LENREP;
             }
             }
             break;
             break;

+ 77 - 91
source/putty/sshpubk.c

@@ -235,8 +235,7 @@ static int rsa1_load_s_internal(BinarySource *src, RSAKey *key, bool pub_only,
         if (enclen & 7)
         if (enclen & 7)
             goto end;
             goto end;
 
 
-        buf = strbuf_new_nm();
-        put_datapl(buf, get_data(src, enclen));
+        buf = strbuf_dup_nm(get_data(src, enclen));
 
 
         { // WINSCP
         { // WINSCP
         unsigned char keybuf[16];
         unsigned char keybuf[16];
@@ -619,6 +618,14 @@ const ssh_keyalg *const all_keyalgs[] = {
     &ssh_ecdsa_nistp521,
     &ssh_ecdsa_nistp521,
     &ssh_ecdsa_ed25519,
     &ssh_ecdsa_ed25519,
     &ssh_ecdsa_ed448,
     &ssh_ecdsa_ed448,
+    &opensshcert_ssh_dsa,
+    &opensshcert_ssh_rsa,
+    &opensshcert_ssh_rsa_sha256,
+    &opensshcert_ssh_rsa_sha512,
+    &opensshcert_ssh_ecdsa_ed25519,
+    &opensshcert_ssh_ecdsa_nistp256,
+    &opensshcert_ssh_ecdsa_nistp384,
+    &opensshcert_ssh_ecdsa_nistp521,
 };
 };
 const size_t n_keyalgs = lenof(all_keyalgs);
 const size_t n_keyalgs = lenof(all_keyalgs);
 
 
@@ -637,6 +644,18 @@ const ssh_keyalg *find_pubkey_alg(const char *name)
     return find_pubkey_alg_len(ptrlen_from_asciz(name));
     return find_pubkey_alg_len(ptrlen_from_asciz(name));
 }
 }
 
 
+ptrlen pubkey_blob_to_alg_name(ptrlen blob)
+{
+    BinarySource src[1];
+    BinarySource_BARE_INIT_PL(src, blob);
+    return get_string(src);
+}
+
+const ssh_keyalg *pubkey_blob_to_alg(ptrlen blob)
+{
+    return find_pubkey_alg_len(pubkey_blob_to_alg_name(blob));
+}
+
 struct ppk_cipher {
 struct ppk_cipher {
     const char *name;
     const char *name;
     size_t blocklen, keylen, ivlen;
     size_t blocklen, keylen, ivlen;
@@ -1291,7 +1310,7 @@ bool ppk_loadpub_s(BinarySource *src, char **algorithm, BinarySink *bs,
         bool ret = openssh_loadpub(src, algorithm, bs, commentptr, errorstr);
         bool ret = openssh_loadpub(src, algorithm, bs, commentptr, errorstr);
         return ret;
         return ret;
     } else if (type != SSH_KEYTYPE_SSH2) {
     } else if (type != SSH_KEYTYPE_SSH2) {
-        error = "not a PuTTY SSH-2 private key";
+        error = "not a public key or a PuTTY SSH-2 private key";
         goto error;
         goto error;
     }
     }
 
 
@@ -1303,7 +1322,7 @@ bool ppk_loadpub_s(BinarySource *src, char **algorithm, BinarySink *bs,
         if (0 == strncmp(header, "PuTTY-User-Key-File-", 20))
         if (0 == strncmp(header, "PuTTY-User-Key-File-", 20))
             error = "PuTTY key format too new";
             error = "PuTTY key format too new";
         else
         else
-            error = "not a PuTTY SSH-2 private key";
+            error = "not a public key or a PuTTY SSH-2 private key";
         goto error;
         goto error;
     }
     }
     error = "file format error";
     error = "file format error";
@@ -1449,37 +1468,6 @@ int base64_lines(int datalen)
     return (datalen + 47) / 48;
     return (datalen + 47) / 48;
 }
 }
 
 
-static void base64_encode_s(BinarySink *bs, const unsigned char *data,
-                            int datalen, int cpl)
-{
-    int linelen = 0;
-    char out[4];
-    int n, i;
-
-    while (datalen > 0) {
-        n = (datalen < 3 ? datalen : 3);
-        base64_encode_atom(data, n, out);
-        data += n;
-        datalen -= n;
-        for (i = 0; i < 4; i++) {
-            if (linelen >= cpl) {
-                linelen = 0;
-                put_byte(bs, '\n');
-            }
-            put_byte(bs, out[i]);
-            linelen++;
-        }
-    }
-    put_byte(bs, '\n');
-}
-
-void base64_encode(FILE *fp, const unsigned char *data, int datalen, int cpl)
-{
-    stdio_sink ss;
-    stdio_sink_init(&ss, fp);
-    base64_encode_s(BinarySink_UPCAST(&ss), data, datalen, cpl);
-}
-
 const ppk_save_parameters ppk_save_default_parameters = {
 const ppk_save_parameters ppk_save_default_parameters = {
     // WINSCP
     // WINSCP
     /*.fmt_version =*/ 3,
     /*.fmt_version =*/ 3,
@@ -1629,7 +1617,7 @@ strbuf *ppk_save_sb(ssh2_userkey *key, const char *passphrase,
     put_fmt(out, "Encryption: %s\n", cipherstr);
     put_fmt(out, "Encryption: %s\n", cipherstr);
     put_fmt(out, "Comment: %s\n", key->comment);
     put_fmt(out, "Comment: %s\n", key->comment);
     put_fmt(out, "Public-Lines: %d\n", base64_lines(pub_blob->len));
     put_fmt(out, "Public-Lines: %d\n", base64_lines(pub_blob->len));
-    base64_encode_s(BinarySink_UPCAST(out), pub_blob->u, pub_blob->len, 64);
+    base64_encode_bs(BinarySink_UPCAST(out), ptrlen_from_strbuf(pub_blob), 64);
     if (params.fmt_version == 3 && ciphertype->keylen != 0) {
     if (params.fmt_version == 3 && ciphertype->keylen != 0) {
         put_fmt(out, "Key-Derivation: %s\n",
         put_fmt(out, "Key-Derivation: %s\n",
                 params.argon2_flavour == Argon2d ? "Argon2d" :
                 params.argon2_flavour == Argon2d ? "Argon2d" :
@@ -1648,8 +1636,8 @@ strbuf *ppk_save_sb(ssh2_userkey *key, const char *passphrase,
         } // WINSCP
         } // WINSCP
     }
     }
     put_fmt(out, "Private-Lines: %d\n", base64_lines(priv_encrypted_len));
     put_fmt(out, "Private-Lines: %d\n", base64_lines(priv_encrypted_len));
-    base64_encode_s(BinarySink_UPCAST(out),
-                    priv_blob_encrypted, priv_encrypted_len, 64);
+    base64_encode_bs(BinarySink_UPCAST(out),
+                     make_ptrlen(priv_blob_encrypted, priv_encrypted_len), 64);
     put_fmt(out, "Private-MAC: ");
     put_fmt(out, "Private-MAC: ");
     for (i = 0; i < macalg->len; i++)
     for (i = 0; i < macalg->len; i++)
         put_fmt(out, "%02x", priv_mac[i]);
         put_fmt(out, "%02x", priv_mac[i]);
@@ -1850,6 +1838,7 @@ static void ssh2_fingerprint_blob_sha256(ptrlen blob, strbuf *sb)
 char *ssh2_fingerprint_blob(ptrlen blob, FingerprintType fptype)
 char *ssh2_fingerprint_blob(ptrlen blob, FingerprintType fptype)
 {
 {
     strbuf *sb = strbuf_new();
     strbuf *sb = strbuf_new();
+    strbuf *tmp = NULL;
 
 
     /*
     /*
      * Identify the key algorithm, if possible.
      * Identify the key algorithm, if possible.
@@ -1866,24 +1855,63 @@ char *ssh2_fingerprint_blob(ptrlen blob, FingerprintType fptype)
         if (alg) {
         if (alg) {
             int bits = ssh_key_public_bits(alg, blob);
             int bits = ssh_key_public_bits(alg, blob);
             put_fmt(sb, "%.*s %d ", PTRLEN_PRINTF(algname), bits);
             put_fmt(sb, "%.*s %d ", PTRLEN_PRINTF(algname), bits);
+
+            if (!ssh_fptype_is_cert(fptype) && alg->is_certificate) {
+                ssh_key *key = ssh_key_new_pub(alg, blob);
+                if (key) {
+                    tmp = strbuf_new();
+                    ssh_key_public_blob(ssh_key_base_key(key),
+                                        BinarySink_UPCAST(tmp));
+                    blob = ptrlen_from_strbuf(tmp);
+                    ssh_key_free(key);
+                }
+            }
         } else {
         } else {
             put_fmt(sb, "%.*s ", PTRLEN_PRINTF(algname));
             put_fmt(sb, "%.*s ", PTRLEN_PRINTF(algname));
         }
         }
     }
     }
     } // WINSCP
     } // WINSCP
 
 
-    switch (fptype) {
+    switch (ssh_fptype_from_cert(fptype)) {
       case SSH_FPTYPE_MD5:
       case SSH_FPTYPE_MD5:
         ssh2_fingerprint_blob_md5(blob, sb);
         ssh2_fingerprint_blob_md5(blob, sb);
         break;
         break;
       case SSH_FPTYPE_SHA256:
       case SSH_FPTYPE_SHA256:
         ssh2_fingerprint_blob_sha256(blob, sb);
         ssh2_fingerprint_blob_sha256(blob, sb);
         break;
         break;
+      default:
+        unreachable("ssh_fptype_from_cert ruled out the other values");
     }
     }
 
 
+    if (tmp)
+        strbuf_free(tmp);
+
     return strbuf_to_str(sb);
     return strbuf_to_str(sb);
 }
 }
 
 
+char *ssh2_double_fingerprint_blob(ptrlen blob, FingerprintType fptype)
+{
+    if (ssh_fptype_is_cert(fptype))
+        fptype = ssh_fptype_from_cert(fptype);
+
+    char *fp = ssh2_fingerprint_blob(blob, fptype);
+    char *p = strrchr(fp, ' ');
+    char *hash = p ? p + 1 : fp;
+
+    char *fpc = ssh2_fingerprint_blob(blob, ssh_fptype_to_cert(fptype));
+    char *pc = strrchr(fpc, ' ');
+    char *hashc = pc ? pc + 1 : fpc;
+
+    if (strcmp(hash, hashc)) {
+        char *tmp = dupprintf("%s (with certificate: %s)", fp, hashc);
+        sfree(fp);
+        fp = tmp;
+    }
+
+    sfree(fpc);
+    return fp;
+}
+
 char **ssh2_all_fingerprints_for_blob(ptrlen blob)
 char **ssh2_all_fingerprints_for_blob(ptrlen blob)
 {
 {
     char **fps = snewn(SSH_N_FPTYPES, char *);
     char **fps = snewn(SSH_N_FPTYPES, char *);
@@ -1903,6 +1931,15 @@ char *ssh2_fingerprint(ssh_key *data, FingerprintType fptype)
     return ret;
     return ret;
 }
 }
 
 
+char *ssh2_double_fingerprint(ssh_key *data, FingerprintType fptype)
+{
+    strbuf *blob = strbuf_new();
+    ssh_key_public_blob(data, BinarySink_UPCAST(blob));
+    char *ret = ssh2_double_fingerprint_blob(ptrlen_from_strbuf(blob), fptype);
+    strbuf_free(blob);
+    return ret;
+}
+
 char **ssh2_all_fingerprints(ssh_key *data)
 char **ssh2_all_fingerprints(ssh_key *data)
 {
 {
     strbuf *blob = strbuf_new();
     strbuf *blob = strbuf_new();
@@ -1962,7 +1999,7 @@ static int key_type_s_internal(BinarySource *src)
     if (find_pubkey_alg_len(get_nonchars(src, " \n")) > 0 &&
     if (find_pubkey_alg_len(get_nonchars(src, " \n")) > 0 &&
         get_chars(src, " ").len == 1 &&
         get_chars(src, " ").len == 1 &&
         get_chars(src, "0123456789ABCDEFGHIJKLMNOPQRSTUV"
         get_chars(src, "0123456789ABCDEFGHIJKLMNOPQRSTUV"
-                   "WXYZabcdefghijklmnopqrstuvwxyz+/=").len > 0 &&
+                  "WXYZabcdefghijklmnopqrstuvwxyz+/=").len > 0 &&
         get_nonchars(src, " \n").len == 0)
         get_nonchars(src, " \n").len == 0)
         return SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH;
         return SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH;
 
 
@@ -2031,54 +2068,3 @@ const char *key_type_to_str(int type)
         unreachable("bad key type in key_type_to_str");
         unreachable("bad key type in key_type_to_str");
     }
     }
 }
 }
-
-key_components *key_components_new(void)
-{
-    key_components *kc = snew(key_components);
-    kc->ncomponents = 0;
-    kc->componentsize = 0;
-    kc->components = NULL;
-    return kc;
-}
-
-void key_components_add_text(key_components *kc,
-                             const char *name, const char *value)
-{
-    sgrowarray(kc->components, kc->componentsize, kc->ncomponents);
-    { // WINSCP
-    size_t n = kc->ncomponents++;
-    kc->components[n].name = dupstr(name);
-    kc->components[n].is_mp_int = false;
-    kc->components[n].text = dupstr(value);
-    } // WINSCP
-}
-
-void key_components_add_mp(key_components *kc,
-                           const char *name, mp_int *value)
-{
-    sgrowarray(kc->components, kc->componentsize, kc->ncomponents);
-    { // WINSCP
-    size_t n = kc->ncomponents++;
-    kc->components[n].name = dupstr(name);
-    kc->components[n].is_mp_int = true;
-    kc->components[n].mp = mp_copy(value);
-    } // WINSCP
-}
-
-void key_components_free(key_components *kc)
-{
-    { // WINSCP
-    size_t i;
-    for (i = 0; i < kc->ncomponents; i++) {
-        sfree(kc->components[i].name);
-        if (kc->components[i].is_mp_int) {
-            mp_free(kc->components[i].mp);
-        } else {
-            smemclr(kc->components[i].text, strlen(kc->components[i].text));
-            sfree(kc->components[i].text);
-        }
-    }
-    sfree(kc->components);
-    sfree(kc);
-    } // WINSCP
-}

+ 27 - 0
source/putty/storage.h

@@ -6,6 +6,8 @@
 #ifndef PUTTY_STORAGE_H
 #ifndef PUTTY_STORAGE_H
 #define PUTTY_STORAGE_H
 #define PUTTY_STORAGE_H
 
 
+#include "defs.h"
+
 /* ----------------------------------------------------------------------
 /* ----------------------------------------------------------------------
  * Functions to save and restore PuTTY sessions. Note that this is
  * Functions to save and restore PuTTY sessions. Note that this is
  * only the low-level code to do the reading and writing. The
  * only the low-level code to do the reading and writing. The
@@ -96,6 +98,31 @@ int check_stored_host_key(const char *hostname, int port,
 void store_host_key(const char *hostname, int port,
 void store_host_key(const char *hostname, int port,
                     const char *keytype, const char *key);
                     const char *keytype, const char *key);
 
 
+/* ----------------------------------------------------------------------
+ * Functions to access PuTTY's configuration for trusted host
+ * certification authorities. This must be stored separately from the
+ * saved-session data, because the whole point is to avoid having to
+ * configure CAs separately per session.
+ */
+
+struct host_ca {
+    char *name;
+    strbuf *ca_public_key;
+    char *validity_expression;
+    ca_options opts;
+};
+
+host_ca_enum *enum_host_ca_start(void);
+bool enum_host_ca_next(host_ca_enum *handle, strbuf *out);
+void enum_host_ca_finish(host_ca_enum *handle);
+
+host_ca *host_ca_load(const char *name);
+char *host_ca_save(host_ca *);   /* NULL on success, or dynamic error msg */
+char *host_ca_delete(const char *name); /* likewise */
+
+host_ca *host_ca_new(void);  /* initialises to default settings */
+void host_ca_free(host_ca *);
+
 /* ----------------------------------------------------------------------
 /* ----------------------------------------------------------------------
  * Functions to access PuTTY's random number seed file.
  * Functions to access PuTTY's random number seed file.
  */
  */

+ 11 - 0
source/putty/stubs/null-cipher.c

@@ -0,0 +1,11 @@
+/*
+ * Implementation of shared trivial routines that ssh_cipher
+ * implementations might use.
+ */
+
+#include "ssh.h"
+
+void nullcipher_next_message(ssh_cipher *cipher)
+{
+    /* Most ciphers don't do anything at all with this */
+}

+ 22 - 0
source/putty/stubs/null-key.c

@@ -0,0 +1,22 @@
+#include "misc.h"
+#include "ssh.h"
+
+unsigned nullkey_supported_flags(const ssh_keyalg *self)
+{
+    return 0;
+}
+
+const char *nullkey_alternate_ssh_id(const ssh_keyalg *self, unsigned flags)
+{
+    /* There are no alternate ids */
+    return self->ssh_id;
+}
+
+ssh_key *nullkey_base_key(ssh_key *key)
+{
+    /* When a key is not certified, it is its own base */
+    return key;
+}
+
+bool nullkey_variable_size_no(const ssh_keyalg *self) { return false; }
+bool nullkey_variable_size_yes(const ssh_keyalg *self) { return true; }

+ 0 - 0
source/putty/utils/null_lp.c → source/putty/stubs/null-lp.c


+ 11 - 0
source/putty/stubs/null-mac.c

@@ -0,0 +1,11 @@
+/*
+ * Implementation of shared trivial routines that ssh2_mac
+ * implementations might use.
+ */
+
+#include "ssh.h"
+
+void nullmac_next_message(ssh2_mac *m)
+{
+    /* Most MACs don't do anything at all with this */
+}

+ 20 - 0
source/putty/stubs/null-opener.c

@@ -0,0 +1,20 @@
+/*
+ * Null implementation of DeferredSocketOpener. Doesn't even bother to
+ * allocate and free itself: there's just one static implementation
+ * which we hand out to any caller.
+ */
+
+#include "putty.h"
+
+static void null_opener_free(DeferredSocketOpener *opener) {}
+
+static const DeferredSocketOpenerVtable NullOpener_vt = {
+    .free = null_opener_free,
+};
+
+static DeferredSocketOpener null_opener = { .vt = &NullOpener_vt };
+
+DeferredSocketOpener *null_deferred_socket_opener(void)
+{
+    return &null_opener;
+}

+ 0 - 0
source/putty/stubs/nullplug.c → source/putty/stubs/null-plug.c


+ 12 - 1
source/putty/utils/nullseat.c → source/putty/stubs/null-seat.c

@@ -22,7 +22,7 @@ char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; }
 void nullseat_set_busy_status(Seat *seat, BusyStatus status) {}
 void nullseat_set_busy_status(Seat *seat, BusyStatus status) {}
 SeatPromptResult nullseat_confirm_ssh_host_key(
 SeatPromptResult nullseat_confirm_ssh_host_key(
     Seat *seat, const char *host, int port, const char *keytype,
     Seat *seat, const char *host, int port, const char *keytype,
-    char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch,
+    char *keystr, SeatDialogText *text, HelpCtx helpctx,
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
 { return SPR_SW_ABORT("this seat can't handle interactive prompts"); }
 { return SPR_SW_ABORT("this seat can't handle interactive prompts"); }
 SeatPromptResult nullseat_confirm_weak_crypto_primitive(
 SeatPromptResult nullseat_confirm_weak_crypto_primitive(
@@ -52,3 +52,14 @@ bool nullseat_verbose_yes(Seat *seat) { return true; }
 bool nullseat_interactive_no(Seat *seat) { return false; }
 bool nullseat_interactive_no(Seat *seat) { return false; }
 bool nullseat_interactive_yes(Seat *seat) { return true; }
 bool nullseat_interactive_yes(Seat *seat) { return true; }
 bool nullseat_get_cursor_position(Seat *seat, int *x, int *y) { return false; }
 bool nullseat_get_cursor_position(Seat *seat, int *x, int *y) { return false; }
+
+const SeatDialogPromptDescriptions *nullseat_prompt_descriptions(Seat *seat)
+{
+    static const SeatDialogPromptDescriptions descs = {
+        .hk_accept_action = "",
+        .hk_connect_once_action = "",
+        .hk_cancel_action = "",
+        .hk_cancel_action_Participle = "",
+    };
+    return &descs;
+}

+ 11 - 11
source/putty/tree234.h

@@ -45,13 +45,13 @@ tree234 *newtree234(cmpfn234 cmp);
 /*
 /*
  * Free a 2-3-4 tree (not including freeing the elements).
  * Free a 2-3-4 tree (not including freeing the elements).
  */
  */
-void freetree234(tree234 * t);
+void freetree234(tree234 *t);
 
 
 /*
 /*
  * Add an element e to a sorted 2-3-4 tree t. Returns e on success,
  * Add an element e to a sorted 2-3-4 tree t. Returns e on success,
  * or if an existing element compares equal, returns that.
  * or if an existing element compares equal, returns that.
  */
  */
-void *add234(tree234 * t, void *e);
+void *add234(tree234 *t, void *e);
 
 
 /*
 /*
  * Add an element e to an unsorted 2-3-4 tree t. Returns e on
  * Add an element e to an unsorted 2-3-4 tree t. Returns e on
@@ -61,7 +61,7 @@ void *add234(tree234 * t, void *e);
  * Index range can be from 0 to the tree's current element count,
  * Index range can be from 0 to the tree's current element count,
  * inclusive.
  * inclusive.
  */
  */
-void *addpos234(tree234 * t, void *e, int index);
+void *addpos234(tree234 *t, void *e, int index);
 
 
 /*
 /*
  * Look up the element at a given numeric index in a 2-3-4 tree.
  * Look up the element at a given numeric index in a 2-3-4 tree.
@@ -81,7 +81,7 @@ void *addpos234(tree234 * t, void *e, int index);
  *       consume(p);
  *       consume(p);
  *   }
  *   }
  */
  */
-void *index234(tree234 * t, int index);
+void *index234(tree234 *t, int index);
 
 
 /*
 /*
  * Find an element e in a sorted 2-3-4 tree t. Returns NULL if not
  * Find an element e in a sorted 2-3-4 tree t. Returns NULL if not
@@ -126,10 +126,10 @@ void *index234(tree234 * t, int index);
 enum {
 enum {
     REL234_EQ, REL234_LT, REL234_LE, REL234_GT, REL234_GE
     REL234_EQ, REL234_LT, REL234_LE, REL234_GT, REL234_GE
 };
 };
-void *find234(tree234 * t, void *e, cmpfn234 cmp);
-void *findrel234(tree234 * t, void *e, cmpfn234 cmp, int relation);
-void *findpos234(tree234 * t, void *e, cmpfn234 cmp, int *index);
-void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp, int relation,
+void *find234(tree234 *t, void *e, cmpfn234 cmp);
+void *findrel234(tree234 *t, void *e, cmpfn234 cmp, int relation);
+void *findpos234(tree234 *t, void *e, cmpfn234 cmp, int *index);
+void *findrelpos234(tree234 *t, void *e, cmpfn234 cmp, int relation,
                     int *index);
                     int *index);
 
 
 /*
 /*
@@ -184,12 +184,12 @@ void search234_step(search234_state *state, int direction);
  * is out of range (delpos234) or the element is already not in the
  * is out of range (delpos234) or the element is already not in the
  * tree (del234) then they return NULL.
  * tree (del234) then they return NULL.
  */
  */
-void *del234(tree234 * t, void *e);
-void *delpos234(tree234 * t, int index);
+void *del234(tree234 *t, void *e);
+void *delpos234(tree234 *t, int index);
 
 
 /*
 /*
  * Return the total element count of a tree234.
  * Return the total element count of a tree234.
  */
  */
-int count234(tree234 * t);
+int count234(tree234 *t);
 
 
 #endif                          /* TREE234_H */
 #endif                          /* TREE234_H */

+ 37 - 0
source/putty/utils/base64_decode.c

@@ -0,0 +1,37 @@
+#include "misc.h"
+
+void base64_decode_bs(BinarySink *bs, ptrlen input)
+{
+    BinarySource src[1];
+    BinarySource_BARE_INIT_PL(src, input);
+
+    while (get_avail(src)) {
+        char b64atom[4];
+        unsigned char binatom[3];
+
+        for (size_t i = 0; i < 4 ;) {
+            char c = get_byte(src);
+            if (get_err(src))
+                c = '=';
+            if (c == '\n' || c == '\r')
+                continue;
+            b64atom[i++] = c;
+        }
+
+        put_data(bs, binatom, base64_decode_atom(b64atom, binatom));
+    }
+}
+
+void base64_decode_fp(FILE *fp, ptrlen input)
+{
+    stdio_sink ss;
+    stdio_sink_init(&ss, fp);
+    base64_decode_bs(BinarySink_UPCAST(&ss), input);
+}
+
+strbuf *base64_decode_sb(ptrlen input)
+{
+    strbuf *sb = strbuf_new_nm();
+    base64_decode_bs(BinarySink_UPCAST(sb), input);
+    return sb;
+}

+ 40 - 0
source/putty/utils/base64_encode.c

@@ -0,0 +1,40 @@
+#include "misc.h"
+
+void base64_encode_bs(BinarySink *bs, ptrlen input, int cpl)
+{
+    BinarySource src[1];
+    BinarySource_BARE_INIT_PL(src, input);
+    int linelen = 0;
+
+    while (get_avail(src)) {
+        size_t n = get_avail(src) < 3 ? get_avail(src) : 3;
+        ptrlen binatom = get_data(src, n);
+
+        char b64atom[4];
+        base64_encode_atom(binatom.ptr, binatom.len, b64atom);
+        for (size_t i = 0; i < 4; i++) {
+            if (cpl > 0 && linelen >= cpl) {
+                linelen = 0;
+                put_byte(bs, '\n');
+            }
+            put_byte(bs, b64atom[i]);
+            linelen++;
+        }
+    }
+    if (cpl > 0)
+        put_byte(bs, '\n');
+}
+
+void base64_encode_fp(FILE *fp, ptrlen input, int cpl)
+{
+    stdio_sink ss;
+    stdio_sink_init(&ss, fp);
+    base64_encode_bs(BinarySink_UPCAST(&ss), input, cpl);
+}
+
+strbuf *base64_encode_sb(ptrlen input, int cpl)
+{
+    strbuf *sb = strbuf_new_nm();
+    base64_encode_bs(BinarySink_UPCAST(sb), input, cpl);
+    return sb;
+}

+ 968 - 0
source/putty/utils/cert-expr.c

@@ -0,0 +1,968 @@
+/*
+ * Parser for the boolean expression language used to configure what
+ * host names an OpenSSH certificate will be trusted to sign for.
+ */
+
+/*
+
+Language specification
+======================
+
+Outer lexical layer: the input expression is broken up into tokens,
+with any whitespace between them discarded and ignored. The following
+tokens are special:
+
+  ( ) && || !
+
+and the remaining token type is an 'atom', which is any non-empty
+sequence of characters from the following set:
+
+  ABCDEFGHIJKLMNOPQRSTUVWXYZ
+  abcdefghijklmnopqrstuvwxyz
+  0123456789
+  .-_*?[]/:
+
+Inner lexical layer: once the boundaries of an 'atom' token have been
+determined by the outer lex layer, each atom is further classified
+into one of the following subtypes:
+
+ - If it contains no ':' or '/', it's taken to be a wildcard matching
+   hostnames, e.g. "*.example.com".
+
+ - If it begins with 'port:' followed by digits, it's taken to be a
+   single port number specification, e.g. "port:22".
+
+ - If it begins with 'port:' followed by two digit sequences separated
+   by '-', it's taken to be a port number range, e.g. "port:0-1023".
+
+ - Any other atom is reserved for future expansion. (See Rationale.)
+
+Syntax layer: all of those types of atom are interpreted as predicates
+applied to the (hostname, port) data configured for the SSH connection
+for which the certificate is being validated.
+
+Wildcards are handled using the syntax in wildcard.c. The dot-
+separated structure of hostnames is thus not special; the '*' in
+"*.example.com" will match any number of subdomains under example.com.
+
+More complex boolean expressions can be made by combining those
+predicates using the boolean operators and parentheses, in the obvious
+way: && and || are infix operators representing logical AND and OR, !
+is a prefix operator representing logical NOT, and parentheses
+indicate grouping.
+
+Each of && and || can associate freely with itself (that is, you can
+write "a && b && c" without having to parenthesise one or the other
+subexpression). But they are forbidden to associate with _each other_.
+That is, if you write "a && b || c" or "a || b && c", it's a syntax
+error, and you must add parentheses to indicate which operator was
+intended to have the higher priority.
+
+Rationale
+=========
+
+Atoms: restrictions
+-------------------
+
+The characters permitted in the 'atom' token don't include \, even
+though it's a special character defined by wildcard.c. That's because
+in this restricted context wildcards will never need it: no hostname
+contains a literal \, and neither does any hostname contain a literal
+instance of any of the wildcard characters that wildcard.c allows you
+to use \ to escape.
+
+Atoms: future extension
+-----------------------
+
+The specification of the 'atom' token is intended to leave space for
+more than one kind of future extension.
+
+Most obviously, additional special predicates similar to "port:", with
+different disambiguating prefixes. I don't know what things of that
+kind we might need, but space is left for them just in case.
+
+Also, the unused '/' in the permitted-characters spec is intended to
+leave open the possibility of allowing certificate acceptance to be
+based on IP address, because the usual CIDR syntax for specifying IP
+ranges (e.g. "192.168.1.0/24" or "2345:6789:abcd:ef01::/128") would be
+lexed as a single atom under these rules.
+
+For the moment, certificate acceptance rules based on IP address are
+not supported, because it's not clear what the semantics ought to be.
+There are two problems with using IP addresses for this purpose:
+
+ 1. Sometimes they come from the DNS, which means you can't trust
+    them. The whole idea of SSH is to end-to-end authenticate the host
+    key against only the input given _by the user_ to the client. Any
+    additional data provided by the network, such as the result of a
+    DNS lookup, is suspect.
+
+    On the other hand, sometimes the IP address *is* part of the user
+    input, because the user can provide an IP address rather than a
+    hostname as the intended connection destination. So there are two
+    kinds of IP address, and they should very likely be treated
+    differently.
+
+ 2. Sometimes the server's IP address is not even *known* by the
+    client, if you're connecting via a proxy and leaving DNS lookups
+    to the proxy.
+
+So, what should a boolean expression do if it's asked to accept or
+reject based on an IP address, and the IP address is unknown or
+untrustworthy? I'm not sure, and therefore, in the initial version of
+this expression system, I haven't implemented them at all.
+
+But the syntax is still available for a future extension to use, if we
+come up with good answers to these questions.
+
+(One possibility would be to evaluate the whole expression in Kleene
+three-valued logic, so that every subexpression has the possible
+answers TRUE, FALSE and UNKNOWN. If a definite IP address is not
+available, IP address predicates evaluate to UNKNOWN. Then, once the
+expression as a whole is evaluated, fail closed, by interpreting
+UNKNOWN as 'reject'. The effect would be that a positive _or_ negative
+constraint on the IP address would cause rejection if the IP address
+is not reliably known, because once the predicate itself has returned
+UNKNOWN, negating it still gives UNKNOWN. The only way you could still
+accept a certificate in that situation would be if the overall
+structure of the expression meant that the test of the IP address
+couldn't affect the result anyway, e.g. if it was ANDed with another
+subexpression that definitely evaluated to FALSE, or ORed with one
+that evaluated to TRUE. This system seems conceptually elegant to me,
+but the argument against it is that it's complicated and
+counterintuitive, which is not a property you want in something a user
+is writing for security purposes!)
+
+Operator precedence
+-------------------
+
+Why did I choose to make && and || refuse to associate with each
+other, instead of applying the usual C precedence rule that && beats
+||? Because I think the C precedence rule is essentially arbitrary, in
+the sense that when people are writing boolean expressions in practice
+based on predicates from the rest of their program, it's about equally
+common to want to nest an && within an || and vice versa. So the
+default precedence rule only gives the user what they actually wanted
+about 50% of the time, and leads to absent-minded errors about as
+often as it conveniently allows you to omit a pair of parens.
+
+With my mathematician hat on, it's not so arbitrary. I agree that if
+you're *going* to give || and && a relative priority then it makes
+more sense to make && the higher-priority one, because if you're
+thinking algebraically, && is more multiplicative and || is more
+additive. But the pure-maths contexts in which that's convenient have
+nothing to do with general boolean expressions in if statements.
+
+This boolean syntax is still close enough to that of C and its
+derivatives to allow easy enough expression interchange (not counting
+the fact that atoms would need rewriting). Any boolean expression
+structure accepted by this syntax is also legal C and means the same
+thing; any expression structure accepted by C is either legal and
+equivalent in this syntax, or will fail with an error. In no case is
+anything accepted but mapped to a different meaning.
+
+ */
+
+#include "putty.h"
+
+typedef enum Token {
+    TOK_LPAR, TOK_RPAR,
+    TOK_AND, TOK_OR, TOK_NOT,
+    TOK_ATOM,
+    TOK_END, TOK_ERROR
+} Token;
+
+static inline bool is_space(char c)
+{
+    return (c == ' ' || c == '\n' || c == '\r' || c == '\t' ||
+            c == '\f' || c == '\v');
+}
+
+static inline bool is_operator_char(char c)
+{
+    return (c == '(' || c == ')' || c == '&' || c == '|' || c == '!');
+}
+
+static inline bool is_atom_char(char c)
+{
+    return (('A' <= c && c <= 'Z') ||
+            ('a' <= c && c <= 'z') ||
+            ('0' <= c && c <= '9') ||
+            c == '.' || c == '-' || c == '_' || c == '*' || c == '?' ||
+            c == '[' || c == ']' || c == '/' || c == ':');
+}
+
+static Token lex(ptrlen *text, ptrlen *token, char **err)
+{
+    const char *p = text->ptr, *e = p + text->len;
+    Token type = TOK_ERROR;
+
+    /* Skip whitespace */
+    while (p < e && is_space(*p))
+        p++;
+
+    const char *start = p;
+
+    if (!(p < e)) {
+        type = TOK_END;
+        goto out;
+    }
+
+    if (is_operator_char(*p)) {
+        /* Match boolean-expression tokens */
+        static const struct operator {
+            ptrlen text;
+            Token type;
+        } operators[] = {
+            {PTRLEN_DECL_LITERAL("("), TOK_LPAR},
+            {PTRLEN_DECL_LITERAL(")"), TOK_RPAR},
+            {PTRLEN_DECL_LITERAL("&&"), TOK_AND},
+            {PTRLEN_DECL_LITERAL("||"), TOK_OR},
+            {PTRLEN_DECL_LITERAL("!"), TOK_NOT},
+        };
+
+        for (size_t i = 0; i < lenof(operators); i++) {
+            const struct operator *op = &operators[i];
+            if (e - p >= op->text.len &&
+                ptrlen_eq_ptrlen(op->text, make_ptrlen(p, op->text.len))) {
+                p += op->text.len;
+                type = op->type;
+                goto out;
+            }
+        }
+
+        /*
+         * Report an error if one of the operator characters is used
+         * in a way that doesn't match something in that table (e.g. a
+         * single &).
+         */
+        p++;
+        type = TOK_ERROR;
+        *err = dupstr("unrecognised boolean operator");
+        goto out;
+    } else if (is_atom_char(*p)) {
+        /*
+         * Match an 'atom' token, which is any non-empty sequence of
+         * characters from the combined set that allows hostname
+         * wildcards, IP address ranges and special predicates like
+         * port numbers.
+         */
+        do {
+            p++;
+        } while (p < e && is_atom_char(*p));
+
+        type = TOK_ATOM;
+        goto out;
+    } else {
+        /*
+         * Otherwise, report an error.
+         */
+        p++;
+        type = TOK_ERROR;
+        *err = dupstr("unexpected character in expression");
+        goto out;
+    }
+
+  out:
+    *token = make_ptrlen(start, p - start);
+    text->ptr = p;
+    text->len = e - p;
+    return type;
+}
+
+typedef enum Operator {
+    OP_AND, OP_OR, OP_NOT,
+    OP_HOSTNAME_WC, OP_PORT_RANGE
+} Operator;
+
+typedef struct ExprNode ExprNode;
+struct ExprNode {
+    Operator op;
+    ptrlen text;
+    union {
+        struct {
+            /* OP_AND, OP_OR */
+            ExprNode **subexprs;
+            size_t nsubexprs;
+        };
+        struct {
+            /* OP_NOT */
+            ExprNode *subexpr;
+        };
+        struct {
+            /* OP_HOSTNAME_WC */
+            char *wc;
+        };
+        struct {
+            /* OP_PORT_RANGE */
+            unsigned lo, hi;           /* both inclusive */
+        };
+    };
+};
+
+static ExprNode *exprnode_new(Operator op, ptrlen text)
+{
+    ExprNode *en = snew(ExprNode);
+    memset(en, 0, sizeof(*en));
+    en->op = op;
+    en->text = text;
+    return en;
+}
+
+static void exprnode_free(ExprNode *en)
+{
+    switch (en->op) {
+      case OP_AND:
+      case OP_OR:
+        for (size_t i = 0; i < en->nsubexprs; i++)
+            exprnode_free(en->subexprs[i]);
+        sfree(en->subexprs);
+        break;
+      case OP_NOT:
+        exprnode_free(en->subexpr);
+        break;
+      case OP_HOSTNAME_WC:
+        sfree(en->wc);
+        break;
+      case OP_PORT_RANGE:
+        break;
+      default:
+        unreachable("unhandled node type in exprnode_free");
+    }
+
+    sfree(en);
+}
+
+static unsigned ptrlen_to_port_number(ptrlen input)
+{
+    unsigned val = 0;
+    for (const char *p = input.ptr, *end = p + input.len; p < end; p++) {
+        assert('0' <= *p && *p <= '9'); /* expect parser to have checked */
+        val = 10 * val + (*p - '0');
+        if (val >= 65536)
+            val = 65536; /* normalise 'too large' to avoid integer overflow */
+    }
+    return val;
+}
+
+typedef struct ParserState ParserState;
+struct ParserState {
+    ptrlen currtext;
+    Token tok;
+    ptrlen toktext;
+    char *err;
+    ptrlen errloc;
+};
+
+static void error(ParserState *ps, char *errtext, ptrlen errloc)
+{
+    if (!ps->err) {
+        ps->err = errtext;
+        ps->errloc = errloc;
+    } else {
+        sfree(errtext);
+    }
+}
+
+static void advance(ParserState *ps)
+{
+    char *err = NULL;
+    ps->tok = lex(&ps->currtext, &ps->toktext, &err);
+    if (ps->tok == TOK_ERROR)
+        error(ps, err, ps->toktext);
+}
+
+static ExprNode *parse_atom(ParserState *ps);
+static ExprNode *parse_expr(ParserState *ps);
+
+static bool atom_is_hostname_wc(ptrlen toktext)
+{
+    return !ptrlen_contains(toktext, ":/");
+}
+
+static ExprNode *parse_atom(ParserState *ps)
+{
+    if (ps->tok == TOK_LPAR) {
+        ptrlen openpar = ps->toktext;
+        advance(ps);                   /* eat the ( */
+
+        ExprNode *subexpr = parse_expr(ps);
+        if (!subexpr)
+            return NULL;
+
+        if (ps->tok != TOK_RPAR) {
+            error(ps, dupstr("expected ')' after parenthesised subexpression"),
+                  subexpr->text);
+            exprnode_free(subexpr);
+            return NULL;
+        }
+
+        ptrlen closepar = ps->toktext;
+        advance(ps);                   /* eat the ) */
+
+        /* We can reuse the existing AST node, but we need to extend
+         * its bounds within the input expression to include the
+         * parentheses */
+        subexpr->text = make_ptrlen_startend(
+            openpar.ptr, ptrlen_end(closepar));
+        return subexpr;
+    }
+
+    if (ps->tok == TOK_NOT) {
+        ptrlen notloc = ps->toktext;
+        advance(ps);                   /* eat the ! */
+
+        ExprNode *subexpr = parse_atom(ps);
+        if (!subexpr)
+            return NULL;
+
+        ExprNode *en = exprnode_new(
+            OP_NOT, make_ptrlen_startend(
+                notloc.ptr, ptrlen_end(subexpr->text)));
+        en->subexpr = subexpr;
+        return en;
+    }
+
+    if (ps->tok == TOK_ATOM) {
+        if (atom_is_hostname_wc(ps->toktext)) {
+            /* Hostname wildcard. */
+            ExprNode *en = exprnode_new(OP_HOSTNAME_WC, ps->toktext);
+            en->wc = mkstr(ps->toktext);
+            advance(ps);
+            return en;
+        }
+
+        ptrlen tail;
+        if (ptrlen_startswith(ps->toktext, PTRLEN_LITERAL("port:"), &tail)) {
+            /* Port number (single or range). */
+            unsigned lo, hi;
+            char *minus;
+            static const char DIGITS[] = "0123456789\0";
+            bool parse_ok = false;
+
+            if (tail.len > 0 && ptrlen_contains_only(tail, DIGITS)) {
+                lo = ptrlen_to_port_number(tail);
+                if (lo >= 65536) {
+                    error(ps, dupstr("port number too large"), tail);
+                    return NULL;
+                }
+                hi = lo;
+                parse_ok = true;
+            } else if ((minus = memchr(tail.ptr, '-', tail.len)) != NULL) {
+                ptrlen pl_lo = make_ptrlen_startend(tail.ptr, minus);
+                ptrlen pl_hi = make_ptrlen_startend(minus+1, ptrlen_end(tail));
+                if (pl_lo.len > 0 && ptrlen_contains_only(pl_lo, DIGITS) &&
+                    pl_hi.len > 0 && ptrlen_contains_only(pl_hi, DIGITS)) {
+
+                    lo = ptrlen_to_port_number(pl_lo);
+                    if (lo >= 65536) {
+                        error(ps, dupstr("port number too large"), pl_lo);
+                        return NULL;
+                    }
+
+                    hi = ptrlen_to_port_number(pl_hi);
+                    if (hi >= 65536) {
+                        error(ps, dupstr("port number too large"), pl_hi);
+                        return NULL;
+                    }
+
+                    if (hi < lo) {
+                        error(ps, dupstr("port number range is backwards"),
+                              make_ptrlen_startend(pl_lo.ptr,
+                                                   ptrlen_end(pl_hi)));
+                        return NULL;
+                    }
+
+                    parse_ok = true;
+                }
+            }
+
+            if (!parse_ok) {
+                error(ps, dupstr("unable to parse port number specification"),
+                      ps->toktext);
+                return NULL;
+            }
+
+
+            ExprNode *en = exprnode_new(OP_PORT_RANGE, ps->toktext);
+            en->lo = lo;
+            en->hi = hi;
+            advance(ps);
+            return en;
+        }
+    }
+
+    error(ps, dupstr("expected a predicate or a parenthesised subexpression"),
+          ps->toktext);
+    return NULL;
+}
+
+static ExprNode *parse_expr(ParserState *ps)
+{
+    ExprNode *subexpr = parse_atom(ps);
+    if (!subexpr)
+        return NULL;
+
+    if (ps->tok != TOK_AND && ps->tok != TOK_OR)
+        return subexpr;
+
+    Token operator = ps->tok;
+    ExprNode *en = exprnode_new(ps->tok == TOK_AND ? OP_AND : OP_OR,
+                                subexpr->text);
+    size_t subexprs_size = 0;
+
+    sgrowarray(en->subexprs, subexprs_size, en->nsubexprs);
+    en->subexprs[en->nsubexprs++] = subexpr;
+
+    while (true) {
+        advance(ps);                   /* eat the operator */
+
+        subexpr = parse_atom(ps);
+        if (!subexpr) {
+            exprnode_free(en);
+            return NULL;
+        }
+        sgrowarray(en->subexprs, subexprs_size, en->nsubexprs);
+        en->subexprs[en->nsubexprs++] = subexpr;
+        en->text = make_ptrlen_startend(
+            en->text.ptr, ptrlen_end(subexpr->text));
+
+        if (ps->tok != TOK_AND && ps->tok != TOK_OR)
+            return en;
+
+        if (ps->tok != operator) {
+            error(ps, dupstr("expected parentheses to disambiguate && and || "
+                             "on either side of expression"), subexpr->text);
+            exprnode_free(en);
+            return NULL;
+        }
+    }
+}
+
+static ExprNode *parse(ptrlen expr, char **error_msg, ptrlen *error_loc)
+{
+    ParserState ps[1];
+    ps->currtext = expr;
+    ps->err = NULL;
+    advance(ps);
+
+    ExprNode *en = parse_expr(ps);
+    if (en && ps->tok != TOK_END) {
+        error(ps, dupstr("unexpected text at end of expression"),
+              make_ptrlen_startend(ps->toktext.ptr, ptrlen_end(expr)));
+        exprnode_free(en);
+        en = NULL;
+    }
+
+    if (!en) {
+        if (error_msg)
+            *error_msg = ps->err;
+        else
+            sfree(ps->err);
+        if (error_loc)
+            *error_loc = ps->errloc;
+        return NULL;
+    }
+
+    return en;
+}
+
+static bool eval(ExprNode *en, const char *hostname, unsigned port)
+{
+    switch (en->op) {
+      case OP_AND:
+        for (size_t i = 0; i < en->nsubexprs; i++)
+            if (!eval(en->subexprs[i], hostname, port))
+                return false;
+        return true;
+
+      case OP_OR:
+        for (size_t i = 0; i < en->nsubexprs; i++)
+            if (eval(en->subexprs[i], hostname, port))
+                return true;
+        return false;
+
+      case OP_NOT:
+        return !eval(en->subexpr, hostname, port);
+
+      case OP_HOSTNAME_WC:
+        return wc_match(en->wc, hostname);
+
+      case OP_PORT_RANGE:
+        return en->lo <= port && port <= en->hi;
+
+      default:
+        unreachable("unhandled node type in eval");
+    }
+}
+
+bool cert_expr_match_str(const char *expression,
+                         const char *hostname, unsigned port)
+{
+    ExprNode *en = parse(ptrlen_from_asciz(expression), NULL, NULL);
+    if (!en)
+        return false;
+
+    bool matched = eval(en, hostname, port);
+    exprnode_free(en);
+    return matched;
+}
+
+bool cert_expr_valid(const char *expression,
+                     char **error_msg, ptrlen *error_loc)
+{
+    ExprNode *en = parse(ptrlen_from_asciz(expression), error_msg, error_loc);
+    if (en) {
+        exprnode_free(en);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+struct CertExprBuilder {
+    char **wcs;
+    size_t nwcs, wcsize;
+};
+
+CertExprBuilder *cert_expr_builder_new(void)
+{
+    CertExprBuilder *eb = snew(CertExprBuilder);
+    eb->wcs = NULL;
+    eb->nwcs = eb->wcsize = 0;
+    return eb;
+}
+
+void cert_expr_builder_free(CertExprBuilder *eb)
+{
+    for (size_t i = 0; i < eb->nwcs; i++)
+        sfree(eb->wcs[i]);
+    sfree(eb->wcs);
+    sfree(eb);
+}
+
+void cert_expr_builder_add(CertExprBuilder *eb, const char *wildcard)
+{
+    /* Check this wildcard is lexically valid as an atom */
+    ptrlen orig = ptrlen_from_asciz(wildcard), pl = orig;
+    ptrlen toktext;
+    char *err;
+    Token tok = lex(&pl, &toktext, &err);
+    if (!(tok == TOK_ATOM &&
+          toktext.ptr == orig.ptr &&
+          toktext.len == orig.len &&
+          atom_is_hostname_wc(toktext))) {
+        if (tok == TOK_ERROR)
+            sfree(err);
+        return;
+    }
+
+    sgrowarray(eb->wcs, eb->wcsize, eb->nwcs);
+    eb->wcs[eb->nwcs++] = mkstr(orig);
+}
+
+char *cert_expr_expression(CertExprBuilder *eb)
+{
+    strbuf *sb = strbuf_new();
+    for (size_t i = 0; i < eb->nwcs; i++) {
+        if (i)
+            put_dataz(sb, " || ");
+        put_dataz(sb, eb->wcs[i]);
+    }
+    return strbuf_to_str(sb);
+}
+
+#ifdef TEST
+
+void out_of_memory(void) { fprintf(stderr, "out of memory\n"); abort(); }
+
+static void exprnode_dump(BinarySink *bs, ExprNode *en, const char *origtext)
+{
+    put_fmt(bs, "(%zu:%zu ",
+            (size_t)((const char *)en->text.ptr - origtext),
+            (size_t)((const char *)ptrlen_end(en->text) - origtext));
+    switch (en->op) {
+      case OP_AND:
+      case OP_OR:
+        put_dataz(bs, en->op == OP_AND ? "and" : "or");
+        for (size_t i = 0; i < en->nsubexprs; i++) {
+            put_byte(bs, ' ');
+            exprnode_dump(bs, en->subexprs[i], origtext);
+        }
+        break;
+      case OP_NOT:
+        put_dataz(bs, "not ");
+        exprnode_dump(bs, en->subexpr, origtext);
+        break;
+      case OP_HOSTNAME_WC:
+        put_dataz(bs, "host-wc '");
+        put_dataz(bs, en->wc);
+        put_byte(bs, '\'');
+        break;
+      case OP_PORT_RANGE:
+        put_fmt(bs, "port-range %u %u", en->lo, en->hi);
+        break;
+      default:
+        unreachable("unhandled node type in exprnode_dump");
+    }
+    put_byte(bs, ')');
+}
+
+static const struct ParseTest {
+    const char *file;
+    int line;
+    const char *expr, *output;
+} parsetests[] = {
+#define T(expr_, output_) { \
+        .file=__FILE__, .line=__LINE__, .expr=expr_, .output=output_}
+
+    T("*.example.com", "(0:13 host-wc '*.example.com')"),
+    T("port:0", "(0:6 port-range 0 0)"),
+    T("port:22", "(0:7 port-range 22 22)"),
+    T("port:22-22", "(0:10 port-range 22 22)"),
+    T("port:65535", "(0:10 port-range 65535 65535)"),
+    T("port:0-1023", "(0:11 port-range 0 1023)"),
+
+    T("&", "ERR:0:1:unrecognised boolean operator"),
+    T("|", "ERR:0:1:unrecognised boolean operator"),
+    T(";", "ERR:0:1:unexpected character in expression"),
+    T("port:", "ERR:0:5:unable to parse port number specification"),
+    T("port:abc", "ERR:0:8:unable to parse port number specification"),
+    T("port:65536", "ERR:5:10:port number too large"),
+    T("port:65536-65537", "ERR:5:10:port number too large"),
+    T("port:0-65536", "ERR:7:12:port number too large"),
+    T("port:23-22", "ERR:5:10:port number range is backwards"),
+
+    T("a", "(0:1 host-wc 'a')"),
+    T("(a)", "(0:3 host-wc 'a')"),
+    T("((a))", "(0:5 host-wc 'a')"),
+    T(" (\n(\ra\t)\f)\v", "(1:10 host-wc 'a')"),
+    T("a&&b", "(0:4 and (0:1 host-wc 'a') (3:4 host-wc 'b'))"),
+    T("a||b", "(0:4 or (0:1 host-wc 'a') (3:4 host-wc 'b'))"),
+    T("a&&b&&c", "(0:7 and (0:1 host-wc 'a') (3:4 host-wc 'b') (6:7 host-wc 'c'))"),
+    T("a||b||c", "(0:7 or (0:1 host-wc 'a') (3:4 host-wc 'b') (6:7 host-wc 'c'))"),
+    T("a&&(b||c)", "(0:9 and (0:1 host-wc 'a') (3:9 or (4:5 host-wc 'b') (7:8 host-wc 'c')))"),
+    T("a||(b&&c)", "(0:9 or (0:1 host-wc 'a') (3:9 and (4:5 host-wc 'b') (7:8 host-wc 'c')))"),
+    T("(a&&b)||c", "(0:9 or (0:6 and (1:2 host-wc 'a') (4:5 host-wc 'b')) (8:9 host-wc 'c'))"),
+    T("(a||b)&&c", "(0:9 and (0:6 or (1:2 host-wc 'a') (4:5 host-wc 'b')) (8:9 host-wc 'c'))"),
+    T("!a&&b", "(0:5 and (0:2 not (1:2 host-wc 'a')) (4:5 host-wc 'b'))"),
+    T("a&&!b&&c", "(0:8 and (0:1 host-wc 'a') (3:5 not (4:5 host-wc 'b')) (7:8 host-wc 'c'))"),
+    T("!a||b", "(0:5 or (0:2 not (1:2 host-wc 'a')) (4:5 host-wc 'b'))"),
+    T("a||!b||c", "(0:8 or (0:1 host-wc 'a') (3:5 not (4:5 host-wc 'b')) (7:8 host-wc 'c'))"),
+
+    T("", "ERR:0:0:expected a predicate or a parenthesised subexpression"),
+    T("a &&", "ERR:4:4:expected a predicate or a parenthesised subexpression"),
+    T("a ||", "ERR:4:4:expected a predicate or a parenthesised subexpression"),
+    T("a b c d", "ERR:2:7:unexpected text at end of expression"),
+    T("(", "ERR:1:1:expected a predicate or a parenthesised subexpression"),
+    T("(a", "ERR:1:2:expected ')' after parenthesised subexpression"),
+    T("(a b", "ERR:1:2:expected ')' after parenthesised subexpression"),
+    T("a&&b&&c||d||e", "ERR:6:7:expected parentheses to disambiguate && and || on either side of expression"),
+    T("a||b||c&&d&&e", "ERR:6:7:expected parentheses to disambiguate && and || on either side of expression"),
+    T("!", "ERR:1:1:expected a predicate or a parenthesised subexpression"),
+
+    T("!a", "(0:2 not (1:2 host-wc 'a'))"),
+
+#undef T
+};
+
+static const struct EvalTest {
+    const char *file;
+    int line;
+    const char *expr;
+    const char *host;
+    unsigned port;
+    bool output;
+} evaltests[] = {
+#define T(expr_, host_, port_, output_) { \
+        .file=__FILE__, .line=__LINE__, \
+        .expr=expr_, .host=host_, .port=port_, .output=output_}
+
+    T("*.example.com", "hostname.example.com", 22, true),
+    T("*.example.com", "hostname.example.org", 22, false),
+    T("*.example.com", "hostname.dept.example.com", 22, true),
+    T("*.example.com && port:22", "hostname.example.com", 21, false),
+    T("*.example.com && port:22", "hostname.example.com", 22, true),
+    T("*.example.com && port:22", "hostname.example.com", 23, false),
+    T("*.example.com && port:22-24", "hostname.example.com", 21, false),
+    T("*.example.com && port:22-24", "hostname.example.com", 22, true),
+    T("*.example.com && port:22-24", "hostname.example.com", 23, true),
+    T("*.example.com && port:22-24", "hostname.example.com", 24, true),
+    T("*.example.com && port:22-24", "hostname.example.com", 25, false),
+
+    T("*a* && *b* && *c*", "", 22, false),
+    T("*a* && *b* && *c*", "a", 22, false),
+    T("*a* && *b* && *c*", "b", 22, false),
+    T("*a* && *b* && *c*", "c", 22, false),
+    T("*a* && *b* && *c*", "ab", 22, false),
+    T("*a* && *b* && *c*", "ac", 22, false),
+    T("*a* && *b* && *c*", "bc", 22, false),
+    T("*a* && *b* && *c*", "abc", 22, true),
+
+    T("*a* || *b* || *c*", "", 22, false),
+    T("*a* || *b* || *c*", "a", 22, true),
+    T("*a* || *b* || *c*", "b", 22, true),
+    T("*a* || *b* || *c*", "c", 22, true),
+    T("*a* || *b* || *c*", "ab", 22, true),
+    T("*a* || *b* || *c*", "ac", 22, true),
+    T("*a* || *b* || *c*", "bc", 22, true),
+    T("*a* || *b* || *c*", "abc", 22, true),
+
+    T("*a* && !*b* && *c*", "", 22, false),
+    T("*a* && !*b* && *c*", "a", 22, false),
+    T("*a* && !*b* && *c*", "b", 22, false),
+    T("*a* && !*b* && *c*", "c", 22, false),
+    T("*a* && !*b* && *c*", "ab", 22, false),
+    T("*a* && !*b* && *c*", "ac", 22, true),
+    T("*a* && !*b* && *c*", "bc", 22, false),
+    T("*a* && !*b* && *c*", "abc", 22, false),
+
+    T("*a* || !*b* || *c*", "", 22, true),
+    T("*a* || !*b* || *c*", "a", 22, true),
+    T("*a* || !*b* || *c*", "b", 22, false),
+    T("*a* || !*b* || *c*", "c", 22, true),
+    T("*a* || !*b* || *c*", "ab", 22, true),
+    T("*a* || !*b* || *c*", "ac", 22, true),
+    T("*a* || !*b* || *c*", "bc", 22, true),
+    T("*a* || !*b* || *c*", "abc", 22, true),
+
+#undef T
+};
+
+int main(int argc, char **argv)
+{
+    if (argc > 1) {
+        /*
+         * Parse an expression from the command line.
+         */
+
+        ptrlen expr = ptrlen_from_asciz(argv[1]);
+        char *error_msg;
+        ptrlen error_loc;
+        ExprNode *en = parse(expr, &error_msg, &error_loc);
+        if (!en) {
+            fprintf(stderr, "ERR:%zu:%zu:%s\n",
+                    (size_t)((const char *)error_loc.ptr - argv[1]),
+                    (size_t)((const char *)ptrlen_end(error_loc) - argv[1]),
+                    error_msg);
+            fprintf(stderr, "%.*s\n", PTRLEN_PRINTF(expr));
+            for (const char *p = expr.ptr, *e = error_loc.ptr; p<e; p++)
+                fputc(' ', stderr);
+            for (size_t i = 0; i < error_loc.len || i < 1; i++)
+                fputc('^', stderr);
+            fputc('\n', stderr);
+            sfree(error_msg);
+            return 1;
+        }
+
+        if (argc > 2) {
+            /*
+             * Test-evaluate against a host/port pair given on the
+             * command line.
+             */
+            const char *host = argv[2];
+            unsigned port = (argc > 3 ? strtoul(argv[3], NULL, 0) : 22);
+            bool result = eval(en, host, port);
+            printf("%s\n", result ? "accept" : "reject");
+        } else {
+            /*
+             * Just dump the result of parsing the expression.
+             */
+            stdio_sink ss[1];
+            stdio_sink_init(ss, stdout);
+            exprnode_dump(BinarySink_UPCAST(ss), en, expr.ptr);
+            put_byte(ss, '\n');
+        }
+
+        exprnode_free(en);
+
+        return 0;
+    } else {
+        /*
+         * Run our automated tests.
+         */
+        size_t pass = 0, fail = 0;
+
+        for (size_t i = 0; i < lenof(parsetests); i++) {
+            const struct ParseTest *test = &parsetests[i];
+
+            ptrlen expr = ptrlen_from_asciz(test->expr);
+            char *error_msg;
+            ptrlen error_loc;
+            ExprNode *en = parse(expr, &error_msg, &error_loc);
+
+            strbuf *output = strbuf_new();
+            if (!en) {
+                put_fmt(output, "ERR:%zu:%zu:%s",
+                        (size_t)((const char *)error_loc.ptr - test->expr),
+                        (size_t)((const char *)ptrlen_end(error_loc) -
+                                 test->expr),
+                        error_msg);
+                sfree(error_msg);
+            } else {
+                exprnode_dump(BinarySink_UPCAST(output), en, expr.ptr);
+                exprnode_free(en);
+            }
+
+            if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(output),
+                                 ptrlen_from_asciz(test->output))) {
+                pass++;
+            } else {
+                fprintf(stderr, "FAIL: parsetests[%zu] @ %s:%d:\n"
+                        "  expression: %s\n"
+                        "  expected:   %s\n"
+                        "  actual:     %s\n",
+                        i, test->file, test->line, test->expr,
+                        test->output, output->s);
+                fail++;
+            }
+
+            strbuf_free(output);
+        }
+
+        for (size_t i = 0; i < lenof(evaltests); i++) {
+            const struct EvalTest *test = &evaltests[i];
+
+            ptrlen expr = ptrlen_from_asciz(test->expr);
+            char *error_msg;
+            ptrlen error_loc;
+            ExprNode *en = parse(expr, &error_msg, &error_loc);
+
+            if (!en) {
+                fprintf(stderr, "FAIL: evaltests[%zu] @ %s:%d:\n"
+                        "  expression:  %s\n"
+                        "  parse error: %zu:%zu:%s\n",
+                        i, test->file, test->line, test->expr,
+                        (size_t)((const char *)error_loc.ptr - test->expr),
+                        (size_t)((const char *)ptrlen_end(error_loc) -
+                                 test->expr),
+                        error_msg);
+                sfree(error_msg);
+            } else {
+                bool output = eval(en, test->host, test->port);
+                if (output == test->output) {
+                    pass++;
+                } else {
+                    fprintf(stderr, "FAIL: evaltests[%zu] @ %s:%d:\n"
+                            "  expression: %s\n"
+                            "  host:       %s\n"
+                            "  port:       %u\n"
+                            "  expected:   %s\n"
+                            "  actual:     %s\n",
+                            i, test->file, test->line, test->expr,
+                            test->host, test->port,
+                            test->output ? "accept" : "reject",
+                            output ? "accept" : "reject");
+                    fail++;
+                }
+                exprnode_free(en);
+            }
+        }
+
+        fprintf(stderr, "pass %zu fail %zu total %zu\n",
+                pass, fail, pass+fail);
+        return fail != 0;
+    }
+}
+
+#endif // TEST

+ 2 - 2
source/putty/utils/conf.c

@@ -336,7 +336,7 @@ char *conf_get_str_str(Conf *conf, int primary, const char *secondary)
 }
 }
 
 
 char *conf_get_str_strs(Conf *conf, int primary,
 char *conf_get_str_strs(Conf *conf, int primary,
-                       char *subkeyin, char **subkeyout)
+                        char *subkeyin, char **subkeyout)
 {
 {
     struct constkey key;
     struct constkey key;
     struct conf_entry *entry;
     struct conf_entry *entry;
@@ -476,7 +476,7 @@ void conf_del_str_str(Conf *conf, int primary, const char *secondary)
         del234(conf->tree, entry);
         del234(conf->tree, entry);
         free_entry(entry);
         free_entry(entry);
     }
     }
- }
+}
 
 
 void conf_set_filename(Conf *conf, int primary, const Filename *value)
 void conf_set_filename(Conf *conf, int primary, const Filename *value)
 {
 {

+ 22 - 0
source/putty/utils/host_ca_new_free.c

@@ -0,0 +1,22 @@
+#include "defs.h"
+#include "misc.h"
+#include "storage.h"
+
+host_ca *host_ca_new(void)
+{
+    host_ca *hca = snew(host_ca);
+    memset(hca, 0, sizeof(*hca));
+    hca->opts.permit_rsa_sha1 = false;
+    hca->opts.permit_rsa_sha256 = true;
+    hca->opts.permit_rsa_sha512 = true;
+    return hca;
+}
+
+void host_ca_free(host_ca *hca)
+{
+    sfree(hca->name);
+    sfree(hca->validity_expression);
+    if (hca->ca_public_key)
+        strbuf_free(hca->ca_public_key);
+    sfree(hca);
+}

+ 93 - 0
source/putty/utils/key_components.c

@@ -0,0 +1,93 @@
+#include "ssh.h"
+#include "mpint.h"
+
+key_components *key_components_new(void)
+{
+    key_components *kc = snew(key_components);
+    kc->ncomponents = 0;
+    kc->componentsize = 0;
+    kc->components = NULL;
+    return kc;
+}
+
+static void key_components_add_str(key_components *kc, const char *name,
+                                   KeyComponentType type, ptrlen data)
+{
+    sgrowarray(kc->components, kc->componentsize, kc->ncomponents);
+    size_t n = kc->ncomponents++;
+    kc->components[n].name = dupstr(name);
+    kc->components[n].type = type;
+    kc->components[n].str = strbuf_dup_nm(data);
+}
+
+void key_components_add_text(key_components *kc,
+                             const char *name, const char *value)
+{
+    key_components_add_str(kc, name, KCT_TEXT, ptrlen_from_asciz(value));
+}
+
+void key_components_add_text_pl(key_components *kc,
+                                const char *name, ptrlen value)
+{
+    key_components_add_str(kc, name, KCT_TEXT, value);
+}
+
+void key_components_add_binary(key_components *kc,
+                               const char *name, ptrlen value)
+{
+    key_components_add_str(kc, name, KCT_BINARY, value);
+}
+
+void key_components_add_mp(key_components *kc,
+                           const char *name, mp_int *value)
+{
+    sgrowarray(kc->components, kc->componentsize, kc->ncomponents);
+    size_t n = kc->ncomponents++;
+    kc->components[n].name = dupstr(name);
+    kc->components[n].type = KCT_MPINT;
+    kc->components[n].mp = mp_copy(value);
+}
+
+void key_components_add_uint(key_components *kc,
+                             const char *name, uintmax_t value)
+{
+    mp_int *mpvalue = mp_from_integer(value);
+    key_components_add_mp(kc, name, mpvalue);
+    mp_free(mpvalue);
+}
+
+void key_components_add_copy(key_components *kc,
+                             const char *name, const key_component *value)
+{
+    switch (value->type) {
+      case KCT_TEXT:
+      case KCT_BINARY:
+        key_components_add_str(kc, name, value->type,
+                               ptrlen_from_strbuf(value->str));
+        break;
+      case KCT_MPINT:
+        key_components_add_mp(kc, name, value->mp);
+        break;
+    }
+}
+
+void key_components_free(key_components *kc)
+{
+    for (size_t i = 0; i < kc->ncomponents; i++) {
+        key_component *comp = &kc->components[i];
+        sfree(comp->name);
+        switch (comp->type) {
+          case KCT_MPINT:
+            mp_free(comp->mp);
+            break;
+          case KCT_TEXT:
+          case KCT_BINARY:
+            strbuf_free(comp->str);
+            break;
+          default:
+            unreachable("bad key component type");
+        }
+    }
+    sfree(kc->components);
+    sfree(kc);
+}

+ 9 - 2
source/putty/utils/log_proxy_stderr.c

@@ -7,6 +7,12 @@
 void psb_init(ProxyStderrBuf *psb)
 void psb_init(ProxyStderrBuf *psb)
 {
 {
     psb->size = 0;
     psb->size = 0;
+    psb->prefix = "proxy";
+}
+
+void psb_set_prefix(ProxyStderrBuf *psb, const char *prefix)
+{
+    psb->prefix = prefix;
 }
 }
 
 
 void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb,
 void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb,
@@ -61,7 +67,7 @@ void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb,
                 endpos--;
                 endpos--;
             { // WINSCP
             { // WINSCP
             char *msg = dupprintf(
             char *msg = dupprintf(
-                "proxy: %.*s", (int)(endpos - pos), psb->buf + pos);
+                "%s: %.*s", psb->prefix, (int)(endpos - pos), psb->buf + pos);
             plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0);
             plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0);
             sfree(msg);
             sfree(msg);
 
 
@@ -78,7 +84,8 @@ void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb,
          */
          */
         if (pos == 0 && psb->size == lenof(psb->buf)) {
         if (pos == 0 && psb->size == lenof(psb->buf)) {
             char *msg = dupprintf(
             char *msg = dupprintf(
-                "proxy (partial line): %.*s", (int)psb->size, psb->buf);
+                "%s (partial line): %.*s", psb->prefix, (int)psb->size,
+                psb->buf);
             plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0);
             plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0);
             sfree(msg);
             sfree(msg);
 
 

+ 41 - 0
source/putty/utils/percent_decode.c

@@ -0,0 +1,41 @@
+/*
+ * Decode %-encoding in URL style.
+ */
+
+#include <ctype.h>
+
+#include "misc.h"
+
+void percent_decode_bs(BinarySink *bs, ptrlen data)
+{
+    for (const char *p = data.ptr, *e = ptrlen_end(data); p < e; p++) {
+        char c = *p;
+        if (c == '%' && e-p >= 3 &&
+            isxdigit((unsigned char)p[1]) &&
+            isxdigit((unsigned char)p[2])) {
+            char hex[3];
+            hex[0] = p[1];
+            hex[1] = p[2];
+            hex[2] = '\0';
+            put_byte(bs, strtoul(hex, NULL, 16));
+            p += 2;
+        } else {
+            put_byte(bs, c);
+        }
+    }
+
+}
+
+void percent_decode_fp(FILE *fp, ptrlen data)
+{
+    stdio_sink ss;
+    stdio_sink_init(&ss, fp);
+    percent_decode_bs(BinarySink_UPCAST(&ss), data);
+}
+
+strbuf *percent_decode_sb(ptrlen data)
+{
+    strbuf *sb = strbuf_new();
+    percent_decode_bs(BinarySink_UPCAST(sb), data);
+    return sb;
+}

+ 34 - 0
source/putty/utils/percent_encode.c

@@ -0,0 +1,34 @@
+/*
+ * %-encoding in URL style.
+ *
+ * Defaults to escaping % itself (necessary for decoding to even
+ * work), and any C0 escape character. Further bad characters can be
+ * provided in 'badchars'.
+ */
+
+#include "misc.h"
+
+void percent_encode_bs(BinarySink *bs, ptrlen data, const char *badchars)
+{
+    for (const char *p = data.ptr, *e = ptrlen_end(data); p < e; p++) {
+        char c = *p;
+        if (c == '%' || c < ' ' || (badchars && strchr(badchars, c)))
+            put_fmt(bs, "%%%02X", (unsigned char)c);
+        else
+            put_byte(bs, c);
+    }
+}
+
+void percent_encode_fp(FILE *fp, ptrlen data, const char *badchars)
+{
+    stdio_sink ss;
+    stdio_sink_init(&ss, fp);
+    percent_encode_bs(BinarySink_UPCAST(&ss), data, badchars);
+}
+
+strbuf *percent_encode_sb(ptrlen data, const char *badchars)
+{
+    strbuf *sb = strbuf_new();
+    percent_encode_bs(BinarySink_UPCAST(sb), data, badchars);
+    return sb;
+}

+ 16 - 0
source/putty/utils/ptrlen.c

@@ -54,6 +54,22 @@ bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail)
     return false;
     return false;
 }
 }
 
 
+bool ptrlen_contains(ptrlen input, const char *characters)
+{
+    for (const char *p = input.ptr, *end = p + input.len; p < end; p++)
+        if (strchr(characters, *p))
+            return true;
+    return false;
+}
+
+bool ptrlen_contains_only(ptrlen input, const char *characters)
+{
+    for (const char *p = input.ptr, *end = p + input.len; p < end; p++)
+        if (!strchr(characters, *p))
+            return false;
+    return true;
+}
+
 ptrlen ptrlen_get_word(ptrlen *input, const char *separators)
 ptrlen ptrlen_get_word(ptrlen *input, const char *separators)
 {
 {
     const char *p = input->ptr, *end = p + input->len;
     const char *p = input->ptr, *end = p + input->len;

+ 41 - 0
source/putty/utils/seat_dialog_text.c

@@ -0,0 +1,41 @@
+/*
+ * Helper routines for dealing with SeatDialogText structures.
+ */
+
+#include <stdarg.h>
+
+#include "putty.h"
+
+SeatDialogText *seat_dialog_text_new(void)
+{
+    SeatDialogText *sdt = snew(SeatDialogText);
+    sdt->nitems = sdt->itemsize = 0;
+    sdt->items = NULL;
+    return sdt;
+}
+
+void seat_dialog_text_free(SeatDialogText *sdt)
+{
+    for (size_t i = 0; i < sdt->nitems; i++)
+        sfree(sdt->items[i].text);
+    sfree(sdt->items);
+    sfree(sdt);
+}
+
+static void seat_dialog_text_append_v(
+    SeatDialogText *sdt, SeatDialogTextType type, const char *fmt, va_list ap)
+{
+    sgrowarray(sdt->items, sdt->itemsize, sdt->nitems);
+    SeatDialogTextItem *item = &sdt->items[sdt->nitems++];
+    item->type = type;
+    item->text = dupvprintf(fmt, ap);
+}
+
+void seat_dialog_text_append(SeatDialogText *sdt, SeatDialogTextType type,
+                             const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    seat_dialog_text_append_v(sdt, type, fmt, ap);
+    va_end(ap);
+}

+ 1 - 1
source/putty/utils/smemeq.c

@@ -8,7 +8,7 @@
 #include "defs.h"
 #include "defs.h"
 #include "misc.h"
 #include "misc.h"
 
 
-bool smemeq(const void *av, const void *bv, size_t len)
+unsigned smemeq(const void *av, const void *bv, size_t len)
 {
 {
     const unsigned char *a = (const unsigned char *)av;
     const unsigned char *a = (const unsigned char *)av;
     const unsigned char *b = (const unsigned char *)bv;
     const unsigned char *b = (const unsigned char *)bv;

+ 14 - 0
source/putty/utils/strbuf.c

@@ -112,3 +112,17 @@ void strbuf_finalise_agent_query(strbuf *buf_o)
     assert(buf->visible.len >= 5);
     assert(buf->visible.len >= 5);
     PUT_32BIT_MSB_FIRST(buf->visible.u, buf->visible.len - 4);
     PUT_32BIT_MSB_FIRST(buf->visible.u, buf->visible.len - 4);
 }
 }
+
+strbuf *strbuf_dup(ptrlen string)
+{
+    strbuf *buf = strbuf_new();
+    put_datapl(buf, string);
+    return buf;
+}
+
+strbuf *strbuf_dup_nm(ptrlen string)
+{
+    strbuf *buf = strbuf_new_nm();
+    put_datapl(buf, string);
+    return buf;
+}

+ 13 - 1
source/putty/utils/tempseat.c

@@ -204,6 +204,17 @@ static bool tempseat_has_mixed_input_stream(Seat *seat)
     return seat_has_mixed_input_stream(ts->realseat);
     return seat_has_mixed_input_stream(ts->realseat);
 }
 }
 
 
+static const SeatDialogPromptDescriptions *tempseat_prompt_descriptions(
+    Seat *seat)
+{
+    /* It might be OK to put this in the 'unreachable' category, but I
+     * think it's equally good to put it here, which allows for
+     * someone _preparing_ a prompt right now that they intend to
+     * present once the TempSeat has given way to the real one. */
+    TempSeat *ts = container_of(seat, TempSeat, seat);
+    return seat_prompt_descriptions(ts->realseat);
+}
+
 /* ----------------------------------------------------------------------
 /* ----------------------------------------------------------------------
  * Methods that should never be called on a TempSeat, so we can put an
  * Methods that should never be called on a TempSeat, so we can put an
  * unreachable() in them.
  * unreachable() in them.
@@ -237,7 +248,7 @@ static size_t tempseat_banner(Seat *seat, const void *data, size_t len)
 
 
 static SeatPromptResult tempseat_confirm_ssh_host_key(
 static SeatPromptResult tempseat_confirm_ssh_host_key(
     Seat *seat, const char *host, int port, const char *keytype,
     Seat *seat, const char *host, int port, const char *keytype,
-    char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch,
+    char *keystr, SeatDialogText *text, HelpCtx helpctx,
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
 {
 {
     unreachable("confirm_ssh_host_key should never be called on TempSeat");
     unreachable("confirm_ssh_host_key should never be called on TempSeat");
@@ -323,6 +334,7 @@ static const struct SeatVtable tempseat_vt = {
     /*.confirm_ssh_host_key =*/ tempseat_confirm_ssh_host_key,
     /*.confirm_ssh_host_key =*/ tempseat_confirm_ssh_host_key,
     /*.confirm_weak_crypto_primitive =*/ tempseat_confirm_weak_crypto_primitive,
     /*.confirm_weak_crypto_primitive =*/ tempseat_confirm_weak_crypto_primitive,
     /*.confirm_weak_cached_hostkey =*/ tempseat_confirm_weak_cached_hostkey,
     /*.confirm_weak_cached_hostkey =*/ tempseat_confirm_weak_cached_hostkey,
+    .prompt_descriptions = tempseat_prompt_descriptions,
     /*.is_utf8 =*/ tempseat_is_utf8,
     /*.is_utf8 =*/ tempseat_is_utf8,
     /*.echoedit_update =*/ tempseat_echoedit_update,
     /*.echoedit_update =*/ tempseat_echoedit_update,
     /*.get_x_display =*/ tempseat_get_x_display,
     /*.get_x_display =*/ tempseat_get_x_display,

+ 16 - 16
source/putty/utils/tree234.c

@@ -73,7 +73,7 @@ tree234 *newtree234(cmpfn234 cmp)
 /*
 /*
  * Free a 2-3-4 tree (not including freeing the elements).
  * Free a 2-3-4 tree (not including freeing the elements).
  */
  */
-static void freenode234(node234 * n)
+static void freenode234(node234 *n)
 {
 {
     if (!n)
     if (!n)
         return;
         return;
@@ -84,7 +84,7 @@ static void freenode234(node234 * n)
     sfree(n);
     sfree(n);
 }
 }
 
 
-void freetree234(tree234 * t)
+void freetree234(tree234 *t)
 {
 {
     freenode234(t->root);
     freenode234(t->root);
     sfree(t);
     sfree(t);
@@ -93,7 +93,7 @@ void freetree234(tree234 * t)
 /*
 /*
  * Internal function to count a node.
  * Internal function to count a node.
  */
  */
-static int countnode234(node234 * n)
+static int countnode234(node234 *n)
 {
 {
     int count = 0;
     int count = 0;
     int i;
     int i;
@@ -122,7 +122,7 @@ static int elements234(node234 *n)
 /*
 /*
  * Count the elements in a tree.
  * Count the elements in a tree.
  */
  */
-int count234(tree234 * t)
+int count234(tree234 *t)
 {
 {
     if (t->root)
     if (t->root)
         return countnode234(t->root);
         return countnode234(t->root);
@@ -134,7 +134,7 @@ int count234(tree234 * t)
  * Add an element e to a 2-3-4 tree t. Returns e on success, or if
  * Add an element e to a 2-3-4 tree t. Returns e on success, or if
  * an existing element compares equal, returns that.
  * an existing element compares equal, returns that.
  */
  */
-static void *add234_internal(tree234 * t, void *e, int index)
+static void *add234_internal(tree234 *t, void *e, int index)
 {
 {
     node234 *n, **np, *left, *right;
     node234 *n, **np, *left, *right;
     void *orig_e = e;
     void *orig_e = e;
@@ -463,14 +463,14 @@ static void *add234_internal(tree234 * t, void *e, int index)
     return orig_e;
     return orig_e;
 }
 }
 
 
-void *add234(tree234 * t, void *e)
+void *add234(tree234 *t, void *e)
 {
 {
     if (!t->cmp)                       /* tree is unsorted */
     if (!t->cmp)                       /* tree is unsorted */
         return NULL;
         return NULL;
 
 
     return add234_internal(t, e, -1);
     return add234_internal(t, e, -1);
 }
 }
-void *addpos234(tree234 * t, void *e, int index)
+void *addpos234(tree234 *t, void *e, int index)
 {
 {
     if (index < 0 ||                   /* index out of range */
     if (index < 0 ||                   /* index out of range */
         t->cmp)                        /* tree is sorted */
         t->cmp)                        /* tree is sorted */
@@ -483,7 +483,7 @@ void *addpos234(tree234 * t, void *e, int index)
  * Look up the element at a given numeric index in a 2-3-4 tree.
  * Look up the element at a given numeric index in a 2-3-4 tree.
  * Returns NULL if the index is out of range.
  * Returns NULL if the index is out of range.
  */
  */
-void *index234(tree234 * t, int index)
+void *index234(tree234 *t, int index)
 {
 {
     node234 *n;
     node234 *n;
 
 
@@ -523,7 +523,7 @@ void *index234(tree234 * t, int index)
  * as NULL, in which case the compare function from the tree proper
  * as NULL, in which case the compare function from the tree proper
  * will be used.
  * will be used.
  */
  */
-void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp,
+void *findrelpos234(tree234 *t, void *e, cmpfn234 cmp,
                     int relation, int *index)
                     int relation, int *index)
 {
 {
     search234_state ss;
     search234_state ss;
@@ -598,15 +598,15 @@ void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp,
         *index = ss.index;
         *index = ss.index;
     return toret;
     return toret;
 }
 }
-void *find234(tree234 * t, void *e, cmpfn234 cmp)
+void *find234(tree234 *t, void *e, cmpfn234 cmp)
 {
 {
     return findrelpos234(t, e, cmp, REL234_EQ, NULL);
     return findrelpos234(t, e, cmp, REL234_EQ, NULL);
 }
 }
-void *findrel234(tree234 * t, void *e, cmpfn234 cmp, int relation)
+void *findrel234(tree234 *t, void *e, cmpfn234 cmp, int relation)
 {
 {
     return findrelpos234(t, e, cmp, relation, NULL);
     return findrelpos234(t, e, cmp, relation, NULL);
 }
 }
-void *findpos234(tree234 * t, void *e, cmpfn234 cmp, int *index)
+void *findpos234(tree234 *t, void *e, cmpfn234 cmp, int *index)
 {
 {
     return findrelpos234(t, e, cmp, REL234_EQ, index);
     return findrelpos234(t, e, cmp, REL234_EQ, index);
 }
 }
@@ -686,7 +686,7 @@ void search234_step(search234_state *state, int direction)
  * Delete an element e in a 2-3-4 tree. Does not free the element,
  * Delete an element e in a 2-3-4 tree. Does not free the element,
  * merely removes all links to it from the tree nodes.
  * merely removes all links to it from the tree nodes.
  */
  */
-static void *delpos234_internal(tree234 * t, int index)
+static void *delpos234_internal(tree234 *t, int index)
 {
 {
     node234 *n;
     node234 *n;
     void *retval;
     void *retval;
@@ -1024,13 +1024,13 @@ static void *delpos234_internal(tree234 * t, int index)
         }
         }
     }
     }
 }
 }
-void *delpos234(tree234 * t, int index)
+void *delpos234(tree234 *t, int index)
 {
 {
     if (index < 0 || index >= countnode234(t->root))
     if (index < 0 || index >= countnode234(t->root))
         return NULL;
         return NULL;
     return delpos234_internal(t, index);
     return delpos234_internal(t, index);
 }
 }
-void *del234(tree234 * t, void *e)
+void *del234(tree234 *t, void *e)
 {
 {
     int index;
     int index;
     if (!findrelpos234(t, e, NULL, REL234_EQ, &index))
     if (!findrelpos234(t, e, NULL, REL234_EQ, &index))
@@ -1095,7 +1095,7 @@ typedef struct {
     int elemcount;
     int elemcount;
 } chkctx;
 } chkctx;
 
 
-int chknode(chkctx * ctx, int level, node234 * node,
+int chknode(chkctx *ctx, int level, node234 *node,
             void *lowbound, void *highbound)
             void *lowbound, void *highbound)
 {
 {
     int nkids, nelems;
     int nkids, nelems;

+ 4 - 4
source/putty/version.h

@@ -1,5 +1,5 @@
 /* Generated by automated build script */
 /* Generated by automated build script */
-#define RELEASE 0.77
-#define TEXTVER "Release 0.77"
-#define SSHVER "-Release-0.77"
-#define BINARY_VERSION 0,77,0,0
+#define RELEASE 0.78
+#define TEXTVER "Release 0.78"
+#define SSHVER "-Release-0.78"
+#define BINARY_VERSION 0,78,0,0

+ 2 - 2
source/putty/windows/agent-client.c

@@ -62,8 +62,8 @@ static void wm_copydata_agent_query(strbuf *query, void **out, int *outlen)
             psd = (PSECURITY_DESCRIPTOR)
             psd = (PSECURITY_DESCRIPTOR)
                 LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
                 LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
             if (psd) {
             if (psd) {
-                if (p_InitializeSecurityDescriptor
-                    (psd, SECURITY_DESCRIPTOR_REVISION) &&
+                if (p_InitializeSecurityDescriptor(
+                        psd, SECURITY_DESCRIPTOR_REVISION) &&
                     p_SetSecurityDescriptorOwner(psd, usersid, false)) {
                     p_SetSecurityDescriptorOwner(psd, usersid, false)) {
                     sa.nLength = sizeof(sa);
                     sa.nLength = sizeof(sa);
                     sa.bInheritHandle = true;
                     sa.bInheritHandle = true;

+ 4 - 7
source/putty/windows/gss.c

@@ -94,8 +94,6 @@ static
 #endif
 #endif
 const Ssh_gss_buf gss_mech_krb5={9,"\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"};
 const Ssh_gss_buf gss_mech_krb5={9,"\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"};
 
 
-const char *gsslogmsg = NULL;
-
 static void ssh_sspi_bind_fns(struct ssh_gss_library *lib);
 static void ssh_sspi_bind_fns(struct ssh_gss_library *lib);
 
 
 static tree234 *libraries_to_never_unload;
 static tree234 *libraries_to_never_unload;
@@ -230,9 +228,8 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf, LogContext *logctx) // MPEXT
         lib->gsslogmsg = "Using SSPI from SECUR32.DLL";
         lib->gsslogmsg = "Using SSPI from SECUR32.DLL";
         lib->handle = (void *)module;
         lib->handle = (void *)module;
 
 
-#pragma option push -w-cpt
-        GET_WINDOWS_FUNCTION(module, AcquireCredentialsHandleA);
-#pragma option pop
+        /* No typecheck because Winelib thinks one PVOID is a PLUID */
+        GET_WINDOWS_FUNCTION_NO_TYPECHECK(module, AcquireCredentialsHandleA);
         GET_WINDOWS_FUNCTION(module, InitializeSecurityContextA);
         GET_WINDOWS_FUNCTION(module, InitializeSecurityContextA);
         GET_WINDOWS_FUNCTION(module, FreeContextBuffer);
         GET_WINDOWS_FUNCTION(module, FreeContextBuffer);
         GET_WINDOWS_FUNCTION(module, FreeCredentialsHandle);
         GET_WINDOWS_FUNCTION(module, FreeCredentialsHandle);
@@ -711,8 +708,8 @@ static Ssh_gss_stat ssh_sspi_verify_mic(struct ssh_gss_library *lib,
     InputSecurityToken[1].pvBuffer = mic->value;
     InputSecurityToken[1].pvBuffer = mic->value;
 
 
     winctx->maj_stat = p_VerifySignature(&winctx->context,
     winctx->maj_stat = p_VerifySignature(&winctx->context,
-                                       &InputBufferDescriptor,
-                                       0, &qop);
+                                         &InputBufferDescriptor,
+                                         0, &qop);
     return winctx->maj_stat;
     return winctx->maj_stat;
 }
 }
 
 

+ 7 - 0
source/putty/windows/handle-socket.c

@@ -417,6 +417,13 @@ Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H,
     return &hs->sock;
     return &hs->sock;
 }
 }
 
 
+void handle_socket_set_psb_prefix(Socket *s, const char *prefix)
+{
+    HandleSocket *hs = container_of(s, HandleSocket, sock);
+    assert(hs->sock.vt == &HandleSocket_sockvt);
+    psb_set_prefix(&hs->psb, prefix);
+}
+
 static void sk_handle_deferred_close(Socket *s)
 static void sk_handle_deferred_close(Socket *s)
 {
 {
     HandleSocket *hs = container_of(s, HandleSocket, sock);
     HandleSocket *hs = container_of(s, HandleSocket, sock);

+ 18 - 0
source/putty/windows/local-proxy.c

@@ -102,3 +102,21 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname,
     return socket;
     return socket;
     } // WINSCP
     } // WINSCP
 }
 }
+
+Socket *platform_start_subprocess(const char *cmd, Plug *plug,
+                                  const char *prefix)
+{
+    Socket *socket = make_deferred_handle_socket(
+        null_deferred_socket_opener(),
+        sk_nonamelookup("<local command>"), 0, plug);
+    char *err = platform_setup_local_proxy(socket, cmd);
+    handle_socket_set_psb_prefix(socket, prefix);
+
+    if (err) {
+        sk_close(socket);
+        socket = new_error_socket_fmt(plug, "%s", err);
+        sfree(err);
+    }
+
+    return socket;
+}

+ 289 - 252
source/putty/windows/network.c

@@ -24,6 +24,10 @@
 #endif
 #endif
 #include <ws2tcpip.h>
 #include <ws2tcpip.h>
 
 
+#if HAVE_AFUNIX_H
+#include <afunix.h>
+#endif
+
 #ifndef NO_IPV6
 #ifndef NO_IPV6
 #ifdef __clang__
 #ifdef __clang__
 #pragma clang diagnostic push
 #pragma clang diagnostic push
@@ -83,12 +87,28 @@ struct NetSocket {
     Socket sock;
     Socket sock;
 };
 };
 
 
+/*
+ * Top-level discriminator for SockAddr.
+ *
+ * UNRESOLVED means a host name not yet put through DNS; IP means a
+ * resolved IP address (or list of them); UNIX indicates the AF_UNIX
+ * network family (which Windows also has); NAMEDPIPE indicates that
+ * this SockAddr is phony, holding a Windows named pipe pathname
+ * instead of any address WinSock can understand.
+ */
+typedef enum SuperFamily {
+    UNRESOLVED,
+    IP,
+#if HAVE_AFUNIX_H
+    UNIX,
+#endif
+    NAMEDPIPE
+} SuperFamily;
+
 struct SockAddr {
 struct SockAddr {
     int refcount;
     int refcount;
-    char *error;
-    bool resolved;
-    bool namedpipe; /* indicates that this SockAddr is phony, holding a Windows
-                     * named pipe pathname instead of a network address */
+    const char *error;
+    SuperFamily superfamily;
 #ifndef NO_IPV6
 #ifndef NO_IPV6
     struct addrinfo *ais;              /* Addresses IPv6 style. */
     struct addrinfo *ais;              /* Addresses IPv6 style. */
 #endif
 #endif
@@ -99,18 +119,27 @@ struct SockAddr {
 
 
 /*
 /*
  * Which address family this address belongs to. AF_INET for IPv4;
  * Which address family this address belongs to. AF_INET for IPv4;
- * AF_INET6 for IPv6; AF_UNSPEC indicates that name resolution has
- * not been done and a simple host name is held in this SockAddr
- * structure.
+ * AF_INET6 for IPv6; AF_UNIX for Unix-domain sockets; AF_UNSPEC
+ * indicates that name resolution has not been done and a simple host
+ * name is held in this SockAddr structure.
  */
  */
+static inline int sockaddr_family(SockAddr *addr, SockAddrStep step)
+{
+    switch (addr->superfamily) {
+      case IP:
 #ifndef NO_IPV6
 #ifndef NO_IPV6
-#define SOCKADDR_FAMILY(addr, step) \
-    (!(addr)->resolved ? AF_UNSPEC : \
-     (step).ai ? (step).ai->ai_family : AF_INET)
-#else
-#define SOCKADDR_FAMILY(addr, step) \
-    (!(addr)->resolved ? AF_UNSPEC : AF_INET)
+        if (step.ai)
+            return step.ai->ai_family;
+#endif
+        return AF_INET;
+#if HAVE_AFUNIX_H
+      case UNIX:
+        return AF_UNIX;
 #endif
 #endif
+      default:
+        return AF_UNSPEC;
+    }
+}
 
 
 /*
 /*
  * Start a SockAddrStep structure to step through multiple
  * Start a SockAddrStep structure to step through multiple
@@ -155,16 +184,16 @@ static int cmpforsearch(void *av, void *bv)
 DECL_WINDOWS_FUNCTION(static, int, WSAStartup, (WORD, LPWSADATA));
 DECL_WINDOWS_FUNCTION(static, int, WSAStartup, (WORD, LPWSADATA));
 DECL_WINDOWS_FUNCTION(static, int, WSACleanup, (void));
 DECL_WINDOWS_FUNCTION(static, int, WSACleanup, (void));
 DECL_WINDOWS_FUNCTION(static, int, closesocket, (SOCKET));
 DECL_WINDOWS_FUNCTION(static, int, closesocket, (SOCKET));
-DECL_WINDOWS_FUNCTION(static, u_long, ntohl, (u_long));
-DECL_WINDOWS_FUNCTION(static, u_long, htonl, (u_long));
-DECL_WINDOWS_FUNCTION(static, u_short, htons, (u_short));
-DECL_WINDOWS_FUNCTION(static, u_short, ntohs, (u_short));
+DECL_WINDOWS_FUNCTION(static, ULONG, ntohl, (ULONG));
+DECL_WINDOWS_FUNCTION(static, ULONG, htonl, (ULONG));
+DECL_WINDOWS_FUNCTION(static, USHORT, htons, (USHORT));
+DECL_WINDOWS_FUNCTION(static, USHORT, ntohs, (USHORT));
 DECL_WINDOWS_FUNCTION(static, int, gethostname, (char *, int));
 DECL_WINDOWS_FUNCTION(static, int, gethostname, (char *, int));
 DECL_WINDOWS_FUNCTION(static, struct hostent FAR *, gethostbyname,
 DECL_WINDOWS_FUNCTION(static, struct hostent FAR *, gethostbyname,
                       (const char FAR *));
                       (const char FAR *));
 DECL_WINDOWS_FUNCTION(static, struct servent FAR *, getservbyname,
 DECL_WINDOWS_FUNCTION(static, struct servent FAR *, getservbyname,
                       (const char FAR *, const char FAR *));
                       (const char FAR *, const char FAR *));
-DECL_WINDOWS_FUNCTION(static, unsigned long, inet_addr, (const char FAR *));
+DECL_WINDOWS_FUNCTION(static, ULONG, inet_addr, (const char FAR *));
 DECL_WINDOWS_FUNCTION(static, char FAR *, inet_ntoa, (struct in_addr));
 DECL_WINDOWS_FUNCTION(static, char FAR *, inet_ntoa, (struct in_addr));
 DECL_WINDOWS_FUNCTION(static, const char FAR *, inet_ntop,
 DECL_WINDOWS_FUNCTION(static, const char FAR *, inet_ntop,
                       (int, void FAR *, char *, size_t));
                       (int, void FAR *, char *, size_t));
@@ -183,7 +212,7 @@ DECL_WINDOWS_FUNCTION(static, int, listen, (SOCKET, int));
 DECL_WINDOWS_FUNCTION(static, int, send, (SOCKET, const char FAR *, int, int));
 DECL_WINDOWS_FUNCTION(static, int, send, (SOCKET, const char FAR *, int, int));
 DECL_WINDOWS_FUNCTION(static, int, shutdown, (SOCKET, int));
 DECL_WINDOWS_FUNCTION(static, int, shutdown, (SOCKET, int));
 DECL_WINDOWS_FUNCTION(static, int, ioctlsocket,
 DECL_WINDOWS_FUNCTION(static, int, ioctlsocket,
-                      (SOCKET, long, u_long FAR *));
+                      (SOCKET, LONG, ULONG FAR *));
 DECL_WINDOWS_FUNCTION(static, SOCKET, accept,
 DECL_WINDOWS_FUNCTION(static, SOCKET, accept,
                       (SOCKET, struct sockaddr FAR *, int FAR *));
                       (SOCKET, struct sockaddr FAR *, int FAR *));
 DECL_WINDOWS_FUNCTION(static, int, getpeername,
 DECL_WINDOWS_FUNCTION(static, int, getpeername,
@@ -199,10 +228,9 @@ DECL_WINDOWS_FUNCTION(static, int, getaddrinfo,
                        const struct addrinfo *hints, struct addrinfo **res));
                        const struct addrinfo *hints, struct addrinfo **res));
 DECL_WINDOWS_FUNCTION(static, void, freeaddrinfo, (struct addrinfo *res));
 DECL_WINDOWS_FUNCTION(static, void, freeaddrinfo, (struct addrinfo *res));
 DECL_WINDOWS_FUNCTION(static, int, getnameinfo,
 DECL_WINDOWS_FUNCTION(static, int, getnameinfo,
-                      (const struct sockaddr FAR * sa, socklen_t salen,
-                       char FAR * host, DWORD hostlen, char FAR * serv,
+                      (const struct sockaddr FAR *sa, socklen_t salen,
+                       char FAR *host, DWORD hostlen, char FAR *serv,
                        DWORD servlen, int flags));
                        DWORD servlen, int flags));
-DECL_WINDOWS_FUNCTION(static, char *, gai_strerror, (int ecode));
 DECL_WINDOWS_FUNCTION(static, int, WSAAddressToStringA,
 DECL_WINDOWS_FUNCTION(static, int, WSAAddressToStringA,
                       (LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFO,
                       (LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFO,
                        LPSTR, LPDWORD));
                        LPSTR, LPDWORD));
@@ -250,7 +278,6 @@ void sk_init(void)
     if (!winsock_module)
     if (!winsock_module)
     {
     {
         modalfatalbox("Unable to load any WinSock library");
         modalfatalbox("Unable to load any WinSock library");
-
     }
     }
 
 
 #ifndef NO_IPV6
 #ifndef NO_IPV6
@@ -262,7 +289,6 @@ void sk_init(void)
         /* This function would fail its type-check if we did one,
         /* This function would fail its type-check if we did one,
          * because the VS header file provides an inline definition
          * because the VS header file provides an inline definition
          * which is __cdecl instead of WINAPI. */
          * which is __cdecl instead of WINAPI. */
-        GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gai_strerror);
     } else {
     } else {
         /* Fall back to wship6.dll for Windows 2000 */
         /* Fall back to wship6.dll for Windows 2000 */
         wship6_module = load_system32_dll("wship6.dll");
         wship6_module = load_system32_dll("wship6.dll");
@@ -271,7 +297,6 @@ void sk_init(void)
             GET_WINDOWS_FUNCTION(wship6_module, freeaddrinfo);
             GET_WINDOWS_FUNCTION(wship6_module, freeaddrinfo);
             /* See comment above about type check */
             /* See comment above about type check */
             GET_WINDOWS_FUNCTION_NO_TYPECHECK(wship6_module, getnameinfo);
             GET_WINDOWS_FUNCTION_NO_TYPECHECK(wship6_module, getnameinfo);
-            GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gai_strerror);
         } else {
         } else {
         }
         }
     }
     }
@@ -290,10 +315,12 @@ void sk_init(void)
     GET_WINDOWS_FUNCTION(winsock_module, WSAStartup);
     GET_WINDOWS_FUNCTION(winsock_module, WSAStartup);
     GET_WINDOWS_FUNCTION(winsock_module, WSACleanup);
     GET_WINDOWS_FUNCTION(winsock_module, WSACleanup);
     GET_WINDOWS_FUNCTION(winsock_module, closesocket);
     GET_WINDOWS_FUNCTION(winsock_module, closesocket);
-    GET_WINDOWS_FUNCTION(winsock_module, ntohl);
-    GET_WINDOWS_FUNCTION(winsock_module, htonl);
-    GET_WINDOWS_FUNCTION(winsock_module, htons);
-    GET_WINDOWS_FUNCTION(winsock_module, ntohs);
+    /* Winelib maps ntohl and friends to things like
+     * __wine_ulong_swap, which fail these type checks hopelessly */
+    GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, ntohl);
+    GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, htonl);
+    GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, htons);
+    GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, ntohs);
     GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gethostname);
     GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gethostname);
     GET_WINDOWS_FUNCTION(winsock_module, gethostbyname);
     GET_WINDOWS_FUNCTION(winsock_module, gethostbyname);
     GET_WINDOWS_FUNCTION(winsock_module, getservbyname);
     GET_WINDOWS_FUNCTION(winsock_module, getservbyname);
@@ -442,162 +469,130 @@ const char *winsock_error_string(int error)
     return win_strerror(error);
     return win_strerror(error);
 }
 }
 
 
+static inline const char *namelookup_strerror(DWORD err)
+{
+    /* PuTTY has traditionally translated a few of the likely error
+     * messages into more concise strings than the standard Windows ones */
+    return (err == WSAENETDOWN ? "Network is down" :
+            err == WSAHOST_NOT_FOUND ? "Host does not exist" :
+            err == WSATRY_AGAIN ? "Host not found" :
+            win_strerror(err));
+}
+
 SockAddr *sk_namelookup(const char *host, char **canonicalname,
 SockAddr *sk_namelookup(const char *host, char **canonicalname,
                         int address_family)
                         int address_family)
 {
 {
-    SockAddr *ret = snew(SockAddr);
-    unsigned long a;
-    char realhost[8192];
-    int hint_family;
+    *canonicalname = NULL;
 
 
-    /* Default to IPv4. */
-    hint_family = (address_family == ADDRTYPE_IPV4 ? AF_INET :
-#ifndef NO_IPV6
-                   address_family == ADDRTYPE_IPV6 ? AF_INET6 :
-#endif
-                   AF_UNSPEC);
+    SockAddr *addr = snew(SockAddr);
+    memset(addr, 0, sizeof(SockAddr));
+    addr->superfamily = UNRESOLVED;
+    addr->refcount = 1;
 
 
-    /* Clear the structure and default to IPv4. */
-    memset(ret, 0, sizeof(SockAddr));
 #ifndef NO_IPV6
 #ifndef NO_IPV6
-    ret->ais = NULL;
-#endif
-    ret->namedpipe = false;
-    ret->addresses = NULL;
-    ret->resolved = false;
-    ret->refcount = 1;
-    *realhost = '\0';
-
-    if ((a = p_inet_addr(host)) == (unsigned long) INADDR_NONE) {
-        struct hostent *h = NULL;
-        int err = 0;
-#ifndef NO_IPV6
-        /*
-         * Use getaddrinfo when it's available
-         */
-        if (p_getaddrinfo) {
-            struct addrinfo hints;
-            memset(&hints, 0, sizeof(hints));
-            hints.ai_family = hint_family;
-            hints.ai_flags = AI_CANONNAME;
-            {
-                /* strip [] on IPv6 address literals */
-                char *trimmed_host = host_strduptrim(host);
-                err = p_getaddrinfo(trimmed_host, NULL, &hints, &ret->ais);
-                sfree(trimmed_host);
-            }
-            if (err == 0)
-            {
-                ret->resolved = true;
-            }
-        } else
-#endif
-        {
-            /*
-             * Otherwise use the IPv4-only gethostbyname...
-             * (NOTE: we don't use gethostbyname as a fallback!)
-             */
-            if ( (h = p_gethostbyname(host)) )
-                ret->resolved = true;
+    /*
+     * Use getaddrinfo, as long as it's available. This should handle
+     * both IPv4 and IPv6 address literals, and hostnames, in one
+     * unified API.
+     */
+    if (p_getaddrinfo) {
+        struct addrinfo hints;
+        memset(&hints, 0, sizeof(hints));
+        hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET :
+                           address_family == ADDRTYPE_IPV6 ? AF_INET6 :
+                           AF_UNSPEC);
+        hints.ai_flags = AI_CANONNAME;
+        hints.ai_socktype = SOCK_STREAM;
+
+        /* strip [] on IPv6 address literals */
+        char *trimmed_host = host_strduptrim(host);
+        int err = p_getaddrinfo(trimmed_host, NULL, &hints, &addr->ais);
+        sfree(trimmed_host);
+
+        if (addr->ais) {
+            addr->superfamily = IP;
+            if (addr->ais->ai_canonname)
+                *canonicalname = dupstr(addr->ais->ai_canonname);
             else
             else
-                err = p_WSAGetLastError();
+                *canonicalname = dupstr(host);
+        } else {
+            addr->error = namelookup_strerror(err);
         }
         }
-
-        if (!ret->resolved) {
-            ret->error = (err == WSAENETDOWN ? "Network is down" :
-                          err == WSAHOST_NOT_FOUND ? "Host does not exist" :
-                          err == WSATRY_AGAIN ? "Host not found" :
-#ifndef NO_IPV6
-                          p_getaddrinfo&&p_gai_strerror ? p_gai_strerror(err) :
+        return addr;
+    }
 #endif
 #endif
-                          "gethostbyname: unknown error");
-        } else {
-            ret->error = NULL;
 
 
-#ifndef NO_IPV6
-            /* If we got an address info use that... */
-            if (ret->ais) {
-                /* Are we in IPv4 fallback mode? */
-                /* We put the IPv4 address into the a variable so we can further-on use the IPv4 code... */
-                if (ret->ais->ai_family == AF_INET)
-                {
-                    memcpy(&a,
-                           (char *) &((SOCKADDR_IN *) ret->ais->
-                                      ai_addr)->sin_addr, sizeof(a));
-                }
+    /*
+     * Failing that (if IPv6 support was not compiled in, or if
+     * getaddrinfo turned out to be unavailable at run time), try the
+     * old-fashioned approach, which is to start by manually checking
+     * for an IPv4 literal and then use gethostbyname.
+     */
+    unsigned long a = p_inet_addr(host);
+    if (a != (unsigned long) INADDR_NONE) {
+        addr->addresses = snew(unsigned long);
+        addr->naddresses = 1;
+        addr->addresses[0] = p_ntohl(a);
+        addr->superfamily = IP;
+        *canonicalname = dupstr(host);
+        return addr;
+    }
 
 
-                if (ret->ais->ai_canonname)
-                    strncpy(realhost, ret->ais->ai_canonname, lenof(realhost));
-                else
-                    strncpy(realhost, host, lenof(realhost));
-            }
-            /* We used the IPv4-only gethostbyname()... */
-            else
-#endif
-            {
-                int n;
-                for (n = 0; h->h_addr_list[n]; n++);
-                ret->addresses = snewn(n, unsigned long);
-                ret->naddresses = n;
-                for (n = 0; n < ret->naddresses; n++) {
-                    memcpy(&a, h->h_addr_list[n], sizeof(a));
-                    ret->addresses[n] = p_ntohl(a);
-                }
-                memcpy(&a, h->h_addr, sizeof(a));
-                /* This way we are always sure the h->h_name is valid :) */
-                strncpy(realhost, h->h_name, sizeof(realhost));
-            }
+    struct hostent *h = p_gethostbyname(host);
+    if (h) {
+        addr->superfamily = IP;
+
+        size_t n;
+        for (n = 0; h->h_addr_list[n]; n++);
+        addr->addresses = snewn(n, unsigned long);
+        addr->naddresses = n;
+        for (n = 0; n < addr->naddresses; n++) {
+            uint32_t a;
+            memcpy(&a, h->h_addr_list[n], sizeof(a));
+            addr->addresses[n] = p_ntohl(a);
         }
         }
+
+        *canonicalname = dupstr(h->h_name);
     } else {
     } else {
-        /*
-         * This must be a numeric IPv4 address because it caused a
-         * success return from inet_addr.
-         */
-        ret->addresses = snewn(1, unsigned long);
-        ret->naddresses = 1;
-        ret->addresses[0] = p_ntohl(a);
-        ret->resolved = true;
-        strncpy(realhost, host, sizeof(realhost));
+        DWORD err = p_WSAGetLastError();
+        addr->error = namelookup_strerror(err);
     }
     }
-    realhost[lenof(realhost)-1] = '\0';
-    *canonicalname = dupstr(realhost);
-    return ret;
+    return addr;
 }
 }
 
 
-SockAddr *sk_nonamelookup(const char *host)
+static SockAddr *sk_special_addr(SuperFamily superfamily, const char *name)
 {
 {
     SockAddr *ret = snew(SockAddr);
     SockAddr *ret = snew(SockAddr);
     ret->error = NULL;
     ret->error = NULL;
-    ret->resolved = false;
+    ret->superfamily = superfamily;
 #ifndef NO_IPV6
 #ifndef NO_IPV6
     ret->ais = NULL;
     ret->ais = NULL;
 #endif
 #endif
-    ret->namedpipe = false;
     ret->addresses = NULL;
     ret->addresses = NULL;
     ret->naddresses = 0;
     ret->naddresses = 0;
     ret->refcount = 1;
     ret->refcount = 1;
-    strncpy(ret->hostname, host, lenof(ret->hostname));
+    strncpy(ret->hostname, name, lenof(ret->hostname));
     ret->hostname[lenof(ret->hostname)-1] = '\0';
     ret->hostname[lenof(ret->hostname)-1] = '\0';
     return ret;
     return ret;
 }
 }
 
 
+SockAddr *sk_nonamelookup(const char *host)
+{
+    return sk_special_addr(UNRESOLVED, host);
+}
+
 SockAddr *sk_namedpipe_addr(const char *pipename)
 SockAddr *sk_namedpipe_addr(const char *pipename)
 {
 {
-    SockAddr *ret = snew(SockAddr);
-    ret->error = NULL;
-    ret->resolved = false;
-#ifndef NO_IPV6
-    ret->ais = NULL;
-#endif
-    ret->namedpipe = true;
-    ret->addresses = NULL;
-    ret->naddresses = 0;
-    ret->refcount = 1;
-    strncpy(ret->hostname, pipename, lenof(ret->hostname));
-    ret->hostname[lenof(ret->hostname)-1] = '\0';
-    return ret;
+    return sk_special_addr(NAMEDPIPE, pipename);
 }
 }
 
 
+#if HAVE_AFUNIX_H
+SockAddr *sk_unix_addr(const char *sockpath)
+{
+    return sk_special_addr(UNIX, sockpath);
+}
+#endif
+
 static bool sk_nextaddr(SockAddr *addr, SockAddrStep *step)
 static bool sk_nextaddr(SockAddr *addr, SockAddrStep *step)
 {
 {
 #ifndef NO_IPV6
 #ifndef NO_IPV6
@@ -639,7 +634,7 @@ void sk_getaddr(SockAddr *addr, char *buf, int buflen)
         }
         }
     } else
     } else
 #endif
 #endif
-    if (SOCKADDR_FAMILY(addr, step) == AF_INET) {
+    if (sockaddr_family(addr, step) == AF_INET) {
         struct in_addr a;
         struct in_addr a;
         assert(addr->addresses && step.curraddr < addr->naddresses);
         assert(addr->addresses && step.curraddr < addr->naddresses);
         a.s_addr = p_htonl(addr->addresses[step.curraddr]);
         a.s_addr = p_htonl(addr->addresses[step.curraddr]);
@@ -670,7 +665,7 @@ static SockAddr sk_extractaddr_tmp(
 #ifndef NO_IPV6
 #ifndef NO_IPV6
     toret.ais = step->ai;
     toret.ais = step->ai;
 #endif
 #endif
-    if (SOCKADDR_FAMILY(addr, *step) == AF_INET
+    if (sockaddr_family(addr, *step) == AF_INET
 #ifndef NO_IPV6
 #ifndef NO_IPV6
         && !toret.ais
         && !toret.ais
 #endif
 #endif
@@ -682,7 +677,11 @@ static SockAddr sk_extractaddr_tmp(
 
 
 bool sk_addr_needs_port(SockAddr *addr)
 bool sk_addr_needs_port(SockAddr *addr)
 {
 {
-    return !addr->namedpipe;
+    return addr->superfamily != NAMEDPIPE
+#if HAVE_AFUNIX_H
+        && addr->superfamily != UNIX
+#endif
+        ;
 }
 }
 
 
 bool sk_hostname_is_local(const char *name)
 bool sk_hostname_is_local(const char *name)
@@ -730,7 +729,7 @@ bool sk_address_is_local(SockAddr *addr)
     SockAddrStep step;
     SockAddrStep step;
     int family;
     int family;
     START_STEP(addr, step);
     START_STEP(addr, step);
-    family = SOCKADDR_FAMILY(addr, step);
+    family = sockaddr_family(addr, step);
 
 
 #ifndef NO_IPV6
 #ifndef NO_IPV6
     if (family == AF_INET6) {
     if (family == AF_INET6) {
@@ -766,7 +765,7 @@ int sk_addrtype(SockAddr *addr)
     SockAddrStep step;
     SockAddrStep step;
     int family;
     int family;
     START_STEP(addr, step);
     START_STEP(addr, step);
-    family = SOCKADDR_FAMILY(addr, step);
+    family = sockaddr_family(addr, step);
 
 
     return (family == AF_INET ? ADDRTYPE_IPV4 :
     return (family == AF_INET ? ADDRTYPE_IPV4 :
 #ifndef NO_IPV6
 #ifndef NO_IPV6
@@ -780,7 +779,7 @@ void sk_addrcopy(SockAddr *addr, char *buf)
     SockAddrStep step;
     SockAddrStep step;
     int family;
     int family;
     START_STEP(addr, step);
     START_STEP(addr, step);
-    family = SOCKADDR_FAMILY(addr, step);
+    family = sockaddr_family(addr, step);
 
 
     assert(family != AF_UNSPEC);
     assert(family != AF_UNSPEC);
 #ifndef NO_IPV6
 #ifndef NO_IPV6
@@ -941,7 +940,7 @@ static DWORD try_connect(NetSocket *sock,
     /*
     /*
      * Open socket.
      * Open socket.
      */
      */
-    family = SOCKADDR_FAMILY(sock->addr, sock->step);
+    family = sockaddr_family(sock->addr, sock->step);
 
 
     /*
     /*
      * Remove the socket from the tree before we overwrite its
      * Remove the socket from the tree before we overwrite its
@@ -960,7 +959,7 @@ static DWORD try_connect(NetSocket *sock,
         goto ret;
         goto ret;
     }
     }
 
 
-        SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0);
+    SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0);
 
 
     if (sock->oobinline) {
     if (sock->oobinline) {
         BOOL b = true;
         BOOL b = true;
@@ -1157,7 +1156,7 @@ static DWORD try_connect(NetSocket *sock,
 
 
     err = 0;
     err = 0;
 
 
-    ret:
+  ret:
 
 
     /*
     /*
      * No matter what happened, put the socket back in the tree.
      * No matter what happened, put the socket back in the tree.
@@ -1226,21 +1225,27 @@ Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline,
     return &ret->sock;
     return &ret->sock;
 }
 }
 
 
-Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
-                       bool local_host_only, int orig_address_family)
+static Socket *sk_newlistener_internal(
+    const char *srcaddr, int port, Plug *plug,
+    bool local_host_only, int orig_address_family)
 {
 {
     SOCKET s;
     SOCKET s;
+    SOCKADDR_IN a;
 #ifndef NO_IPV6
 #ifndef NO_IPV6
     SOCKADDR_IN6 a6;
     SOCKADDR_IN6 a6;
 #endif
 #endif
-    SOCKADDR_IN a;
+#if HAVE_AFUNIX_H
+    SOCKADDR_UN au;
+#endif
+    struct sockaddr *bindaddr;
+    unsigned bindsize;
 
 
     DWORD err;
     DWORD err;
     const char *errstr;
     const char *errstr;
     NetSocket *ret;
     NetSocket *ret;
     int retcode;
     int retcode;
 
 
-    int address_family;
+    int address_family = orig_address_family;
 
 
     /*
     /*
      * Create NetSocket structure.
      * Create NetSocket structure.
@@ -1260,16 +1265,6 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
     ret->parent = ret->child = NULL;
     ret->parent = ret->child = NULL;
     ret->addr = NULL;
     ret->addr = NULL;
 
 
-    /*
-     * Translate address_family from platform-independent constants
-     * into local reality.
-     */
-    address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET :
-#ifndef NO_IPV6
-                      orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 :
-#endif
-                      AF_UNSPEC);
-
     /*
     /*
      * Our default, if passed the `don't care' value
      * Our default, if passed the `don't care' value
      * ADDRTYPE_UNSPEC, is to listen on IPv4. If IPv6 is supported,
      * ADDRTYPE_UNSPEC, is to listen on IPv4. If IPv6 is supported,
@@ -1295,85 +1290,100 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
 
 
     ret->oobinline = false;
     ret->oobinline = false;
 
 
+#if HAVE_AFUNIX_H
+    if (address_family != AF_UNIX)
+#endif
     {
     {
         BOOL on = true;
         BOOL on = true;
         p_setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
         p_setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
                      (const char *)&on, sizeof(on));
                      (const char *)&on, sizeof(on));
     }
     }
 
 
+    switch (address_family) {
 #ifndef NO_IPV6
 #ifndef NO_IPV6
-        if (address_family == AF_INET6) {
-            memset(&a6, 0, sizeof(a6));
-            a6.sin6_family = AF_INET6;
-            if (local_host_only)
-                a6.sin6_addr = in6addr_loopback;
-            else
-                a6.sin6_addr = in6addr_any;
-            if (srcaddr != NULL && p_getaddrinfo) {
-                struct addrinfo hints;
-                struct addrinfo *ai;
-                int err;
-
-                memset(&hints, 0, sizeof(hints));
-                hints.ai_family = AF_INET6;
-                hints.ai_flags = 0;
-                {
-                    /* strip [] on IPv6 address literals */
-                    char *trimmed_addr = host_strduptrim(srcaddr);
-                    err = p_getaddrinfo(trimmed_addr, NULL, &hints, &ai);
-                    sfree(trimmed_addr);
-                }
-                if (err == 0 && ai->ai_family == AF_INET6) {
-                    a6.sin6_addr =
-                        ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr;
-                }
-            }
-            a6.sin6_port = p_htons(port);
-        } else
-#endif
-        {
-            bool got_addr = false;
-            a.sin_family = AF_INET;
+      case AF_INET6: {
+        memset(&a6, 0, sizeof(a6));
+        a6.sin6_family = AF_INET6;
+        if (local_host_only)
+            a6.sin6_addr = in6addr_loopback;
+        else
+            a6.sin6_addr = in6addr_any;
+        if (srcaddr != NULL && p_getaddrinfo) {
+            struct addrinfo hints;
+            struct addrinfo *ai;
+            int err;
 
 
-            /*
-             * Bind to source address. First try an explicitly
-             * specified one...
-             */
-            if (srcaddr) {
-                a.sin_addr.s_addr = p_inet_addr(srcaddr);
-                if (a.sin_addr.s_addr != INADDR_NONE) {
-                    /* Override localhost_only with specified listen addr. */
-                    ret->localhost_only = ipv4_is_loopback(a.sin_addr);
-                    got_addr = true;
-                }
+            memset(&hints, 0, sizeof(hints));
+            hints.ai_family = AF_INET6;
+            hints.ai_flags = 0;
+            {
+                /* strip [] on IPv6 address literals */
+                char *trimmed_addr = host_strduptrim(srcaddr);
+                err = p_getaddrinfo(trimmed_addr, NULL, &hints, &ai);
+                sfree(trimmed_addr);
+            }
+            if (err == 0 && ai->ai_family == AF_INET6) {
+                a6.sin6_addr =
+                    ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr;
             }
             }
+        }
+        a6.sin6_port = p_htons(port);
+        bindaddr = (struct sockaddr *)&a6;
+        bindsize = sizeof(a6);
+        break;
+      }
+#endif
+      case AF_INET: {
+        bool got_addr = false;
+        a.sin_family = AF_INET;
 
 
-            /*
-             * ... and failing that, go with one of the standard ones.
-             */
-            if (!got_addr) {
-                if (local_host_only)
-                    a.sin_addr.s_addr = p_htonl(INADDR_LOOPBACK);
-                else
-                    a.sin_addr.s_addr = p_htonl(INADDR_ANY);
+        /*
+         * Bind to source address. First try an explicitly
+         * specified one...
+         */
+        if (srcaddr) {
+            a.sin_addr.s_addr = p_inet_addr(srcaddr);
+            if (a.sin_addr.s_addr != INADDR_NONE) {
+                /* Override localhost_only with specified listen addr. */
+                ret->localhost_only = ipv4_is_loopback(a.sin_addr);
+                got_addr = true;
             }
             }
+        }
 
 
-            a.sin_port = p_htons((short)port);
+        /*
+         * ... and failing that, go with one of the standard ones.
+         */
+        if (!got_addr) {
+            if (local_host_only)
+                a.sin_addr.s_addr = p_htonl(INADDR_LOOPBACK);
+            else
+                a.sin_addr.s_addr = p_htonl(INADDR_ANY);
         }
         }
-#ifndef NO_IPV6
-        retcode = p_bind(s, (address_family == AF_INET6 ?
-                           (struct sockaddr *) &a6 :
-                           (struct sockaddr *) &a),
-                       (address_family ==
-                        AF_INET6 ? sizeof(a6) : sizeof(a)));
-#else
-        retcode = p_bind(s, (struct sockaddr *) &a, sizeof(a));
+
+        a.sin_port = p_htons((short)port);
+        bindaddr = (struct sockaddr *)&a;
+        bindsize = sizeof(a);
+        break;
+      }
+#if HAVE_AFUNIX_H
+      case AF_UNIX: {
+        au.sun_family = AF_UNIX;
+        strncpy(au.sun_path, srcaddr, sizeof(au.sun_path));
+        bindaddr = (struct sockaddr *)&au;
+        bindsize = sizeof(au);
+        break;
+      }
 #endif
 #endif
-        if (retcode != SOCKET_ERROR) {
-            err = 0;
-        } else {
-            err = p_WSAGetLastError();
-        }
+      default:
+        unreachable("bad address family in sk_newlistener_internal");
+    }
+
+    retcode = p_bind(s, bindaddr, bindsize);
+    if (retcode != SOCKET_ERROR) {
+        err = 0;
+    } else {
+        err = p_WSAGetLastError();
+    }
 
 
     if (err) {
     if (err) {
         p_closesocket(s);
         p_closesocket(s);
@@ -1408,9 +1418,9 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
      * If we were given ADDRTYPE_UNSPEC, we must also create an
      * If we were given ADDRTYPE_UNSPEC, we must also create an
      * IPv6 listening socket and link it to this one.
      * IPv6 listening socket and link it to this one.
      */
      */
-    if (address_family == AF_INET && orig_address_family == ADDRTYPE_UNSPEC) {
-        Socket *other = sk_newlistener(srcaddr, port, plug,
-                                       local_host_only, ADDRTYPE_IPV6);
+    if (address_family == AF_INET && orig_address_family == AF_UNSPEC) {
+        Socket *other = sk_newlistener_internal(srcaddr, port, plug,
+                                                local_host_only, AF_INET6);
 
 
         if (other) {
         if (other) {
             NetSocket *ns = container_of(other, NetSocket, sock);
             NetSocket *ns = container_of(other, NetSocket, sock);
@@ -1427,6 +1437,33 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
     return &ret->sock;
     return &ret->sock;
 }
 }
 
 
+Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
+                       bool local_host_only, int orig_address_family)
+{
+    /*
+     * Translate address_family from platform-independent constants
+     * into local reality.
+     */
+    int address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET :
+#ifndef NO_IPV6
+                          orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 :
+#endif
+                          AF_UNSPEC);
+
+    return sk_newlistener_internal(srcaddr, port, plug, local_host_only,
+                                   address_family);
+}
+
+Socket *sk_newlistener_unix(const char *path, Plug *plug)
+{
+#if HAVE_AFUNIX_H
+    return sk_newlistener_internal(path, 0, plug, false, AF_UNIX);
+#else
+    return new_error_socket_fmt(
+        plug, "AF_UNIX support not compiled into this program");
+#endif
+}
+
 static void sk_net_close(Socket *sock)
 static void sk_net_close(Socket *sock)
 {
 {
     NetSocket *s = container_of(sock, NetSocket, sock);
     NetSocket *s = container_of(sock, NetSocket, sock);
@@ -1486,7 +1523,7 @@ static void socket_error_callback(void *vs)
  * The function which tries to send on a socket once it's deemed
  * The function which tries to send on a socket once it's deemed
  * writable.
  * writable.
  */
  */
-void try_send(NetSocket *s)
+static void try_send(NetSocket *s)
 {
 {
     while (s->sending_oob || bufchain_size(&s->output_data) > 0) {
     while (s->sending_oob || bufchain_size(&s->output_data) > 0) {
         int nsent;
         int nsent;
@@ -1780,7 +1817,7 @@ void select_result(WPARAM wParam, LPARAM lParam)
 #ifdef NO_IPV6
 #ifdef NO_IPV6
         struct sockaddr_in isa;
         struct sockaddr_in isa;
 #else
 #else
-        struct sockaddr_storage isa;
+        struct sockaddr_storage isa; // FIXME: also if Unix and no IPv6
 #endif
 #endif
         int addrlen = sizeof(isa);
         int addrlen = sizeof(isa);
         SOCKET t;  /* socket of connection */
         SOCKET t;  /* socket of connection */
@@ -1791,9 +1828,9 @@ void select_result(WPARAM wParam, LPARAM lParam)
         t = p_accept(s->s,(struct sockaddr *)&isa,&addrlen);
         t = p_accept(s->s,(struct sockaddr *)&isa,&addrlen);
         if (t == INVALID_SOCKET)
         if (t == INVALID_SOCKET)
         {
         {
-          err = p_WSAGetLastError();
-          if (err == WSATRY_AGAIN)
-              break;
+            err = p_WSAGetLastError();
+            if (err == WSATRY_AGAIN)
+                break;
         }
         }
 
 
         actx.p = (void *)t;
         actx.p = (void *)t;
@@ -1806,9 +1843,9 @@ void select_result(WPARAM wParam, LPARAM lParam)
         if (s->localhost_only && !ipv4_is_local_addr(isa.sin_addr))
         if (s->localhost_only && !ipv4_is_local_addr(isa.sin_addr))
 #endif
 #endif
         {
         {
-          p_closesocket(t);      /* dodgy WinSock let nonlocal through */
+            p_closesocket(t);      /* dodgy WinSock let nonlocal through */
         } else if (plug_accepting(s->plug, sk_net_accept, actx)) {
         } else if (plug_accepting(s->plug, sk_net_accept, actx)) {
-          p_closesocket(t);      /* denied or error */
+            p_closesocket(t);      /* denied or error */
         }
         }
         break;
         break;
       }
       }
@@ -1836,7 +1873,7 @@ static SocketPeerInfo *sk_net_peer_info(Socket *sock)
 #ifdef NO_IPV6
 #ifdef NO_IPV6
     struct sockaddr_in addr;
     struct sockaddr_in addr;
 #else
 #else
-    struct sockaddr_storage addr;
+    struct sockaddr_storage addr; // FIXME: also if Unix and no IPv6
     char buf[INET6_ADDRSTRLEN];
     char buf[INET6_ADDRSTRLEN];
 #endif
 #endif
     int addrlen = sizeof(addr);
     int addrlen = sizeof(addr);
@@ -1963,7 +2000,7 @@ SockAddr *platform_get_x11_unix_address(const char *display, int displaynum)
 {
 {
     SockAddr *ret = snew(SockAddr);
     SockAddr *ret = snew(SockAddr);
     memset(ret, 0, sizeof(SockAddr));
     memset(ret, 0, sizeof(SockAddr));
-    ret->error = "unix sockets not supported on this platform";
+    ret->error = "unix sockets for X11 not supported on this platform";
     ret->refcount = 1;
     ret->refcount = 1;
     return ret;
     return ret;
 }
 }

+ 2 - 0
source/putty/windows/no-jump-list.c

@@ -3,6 +3,8 @@
  * that don't update the jump list.
  * that don't update the jump list.
  */
  */
 
 
+#include "putty.h"
+
 void add_session_to_jumplist(const char * const sessionname) {}
 void add_session_to_jumplist(const char * const sessionname) {}
 void remove_session_from_jumplist(const char * const sessionname) {}
 void remove_session_from_jumplist(const char * const sessionname) {}
 void clear_jumplist(void) {}
 void clear_jumplist(void) {}

+ 83 - 50
source/putty/windows/platform.h

@@ -37,6 +37,14 @@
 #define BUILDINFO_PLATFORM "Windows"
 #define BUILDINFO_PLATFORM "Windows"
 #endif
 #endif
 
 
+#if defined __GNUC__ || defined __clang__
+#define THREADLOCAL __thread
+#elif defined _MSC_VER
+#define THREADLOCAL __declspec(thread)
+#else
+#error Do not know how to declare thread-local storage with this toolchain
+#endif
+
 /* Randomly-chosen dwData value identifying a WM_COPYDATA message as
 /* Randomly-chosen dwData value identifying a WM_COPYDATA message as
  * being a Pageant transaction */
  * being a Pageant transaction */
 #define AGENT_COPYDATA_ID 0x804e50ba
 #define AGENT_COPYDATA_ID 0x804e50ba
@@ -114,9 +122,13 @@ static inline uintmax_t strtoumax(const char *nptr, char **endptr, int base)
 #endif
 #endif
 #endif
 #endif
 
 
-#define BOXFLAGS DLGWINDOWEXTRA
-#define BOXRESULT (DLGWINDOWEXTRA + sizeof(LONG_PTR))
-#define DF_END 0x0001
+typedef INT_PTR (*ShinyDlgProc)(HWND hwnd, UINT msg, WPARAM wParam,
+                                LPARAM lParam, void *ctx);
+int ShinyDialogBox(HINSTANCE hinst, LPCTSTR tmpl, const char *winclass,
+                   HWND hwndparent, ShinyDlgProc proc, void *ctx);
+void ShinyEndDialog(HWND hwnd, int ret);
+
+void centre_window(HWND hwnd);
 
 
 #ifndef __WINE__
 #ifndef __WINE__
 #ifdef MPEXT
 #ifdef MPEXT
@@ -231,7 +243,7 @@ int has_embedded_chm(void);            /* 1 = yes, 0 = no, -1 = N/A */
  */
  */
 SeatPromptResult win_seat_confirm_ssh_host_key(
 SeatPromptResult win_seat_confirm_ssh_host_key(
     Seat *seat, const char *host, int port, const char *keytype,
     Seat *seat, const char *host, int port, const char *keytype,
-    char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch,
+    char *keystr, SeatDialogText *text, HelpCtx helpctx,
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
 SeatPromptResult win_seat_confirm_weak_crypto_primitive(
 SeatPromptResult win_seat_confirm_weak_crypto_primitive(
     Seat *seat, const char *algtype, const char *algname,
     Seat *seat, const char *algtype, const char *algname,
@@ -239,6 +251,7 @@ SeatPromptResult win_seat_confirm_weak_crypto_primitive(
 SeatPromptResult win_seat_confirm_weak_cached_hostkey(
 SeatPromptResult win_seat_confirm_weak_cached_hostkey(
     Seat *seat, const char *algname, const char *betteralgs,
     Seat *seat, const char *algname, const char *betteralgs,
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat);
 
 
 /*
 /*
  * Windows-specific clipboard helper function shared with dialog.c,
  * Windows-specific clipboard helper function shared with dialog.c,
@@ -302,6 +315,7 @@ void socket_reselect_all(void);
 SockAddr *sk_namedpipe_addr(const char *pipename);
 SockAddr *sk_namedpipe_addr(const char *pipename);
 /* Turn a WinSock error code into a string. */
 /* Turn a WinSock error code into a string. */
 const char *winsock_error_string(int error);
 const char *winsock_error_string(int error);
+Socket *sk_newlistener_unix(const char *socketpath, Plug *plug);
 
 
 /*
 /*
  * network.c dynamically loads WinSock 2 or WinSock 1 depending on
  * network.c dynamically loads WinSock 2 or WinSock 1 depending on
@@ -310,9 +324,9 @@ const char *winsock_error_string(int error);
  * here they are.
  * here they are.
  */
  */
 DECL_WINDOWS_FUNCTION(extern, int, WSAAsyncSelect,
 DECL_WINDOWS_FUNCTION(extern, int, WSAAsyncSelect,
-                      (SOCKET, HWND, u_int, long));
+                      (SOCKET, HWND, u_int, LONG));
 DECL_WINDOWS_FUNCTION(extern, int, WSAEventSelect,
 DECL_WINDOWS_FUNCTION(extern, int, WSAEventSelect,
-                      (SOCKET, WSAEVENT, long));
+                      (SOCKET, WSAEVENT, LONG));
 DECL_WINDOWS_FUNCTION(extern, int, WSAGetLastError, (void));
 DECL_WINDOWS_FUNCTION(extern, int, WSAGetLastError, (void));
 DECL_WINDOWS_FUNCTION(extern, int, WSAEnumNetworkEvents,
 DECL_WINDOWS_FUNCTION(extern, int, WSAEnumNetworkEvents,
                       (SOCKET, WSAEVENT, LPWSANETWORKEVENTS));
                       (SOCKET, WSAEVENT, LPWSANETWORKEVENTS));
@@ -341,6 +355,7 @@ const char *do_select(Plug * plug, SOCKET skt, bool enable); // WINSCP
  */
  */
 void winselgui_set_hwnd(HWND hwnd);
 void winselgui_set_hwnd(HWND hwnd);
 void winselgui_clear_hwnd(void);
 void winselgui_clear_hwnd(void);
+void winselgui_response(WPARAM wParam, LPARAM lParam);
 
 
 void winselcli_setup(void);
 void winselcli_setup(void);
 SOCKET winselcli_unique_socket(void);
 SOCKET winselcli_unique_socket(void);
@@ -356,6 +371,7 @@ Socket *make_deferred_handle_socket(DeferredSocketOpener *opener,
                                     SockAddr *addr, int port, Plug *plug);
                                     SockAddr *addr, int port, Plug *plug);
 void setup_handle_socket(Socket *s, HANDLE send_H, HANDLE recv_H,
 void setup_handle_socket(Socket *s, HANDLE send_H, HANDLE recv_H,
                          HANDLE stderr_H, bool overlapped);
                          HANDLE stderr_H, bool overlapped);
+void handle_socket_set_psb_prefix(Socket *s, const char *prefix);
 Socket *new_named_pipe_client(const char *pipename, Plug *plug); /* winnpc */
 Socket *new_named_pipe_client(const char *pipename, Plug *plug); /* winnpc */
 Socket *new_named_pipe_listener(const char *pipename, Plug *plug); /* winnps */
 Socket *new_named_pipe_listener(const char *pipename, Plug *plug); /* winnps */
 
 
@@ -376,7 +392,7 @@ struct ctlpos {
     int ypos, width;
     int ypos, width;
     int xoff;
     int xoff;
     int boxystart, boxid;
     int boxystart, boxid;
-    char *boxtext;
+    const char *boxtext;
 };
 };
 void init_common_controls(void);       /* also does some DLL-loading */
 void init_common_controls(void);       /* also does some DLL-loading */
 
 
@@ -416,7 +432,7 @@ struct dlgparam {
     char *wintitle;                    /* title of actual window */
     char *wintitle;                    /* title of actual window */
     char *errtitle;                    /* title of error sub-messageboxes */
     char *errtitle;                    /* title of error sub-messageboxes */
     void *data;                        /* data to pass in refresh events */
     void *data;                        /* data to pass in refresh events */
-    union control *focused, *lastfocused; /* which ctrl has focus now/before */
+    dlgcontrol *focused, *lastfocused; /* which ctrl has focus now/before */
     bool shortcuts[128];               /* track which shortcuts in use */
     bool shortcuts[128];               /* track which shortcuts in use */
     bool coloursel_wanted;             /* has an event handler asked for
     bool coloursel_wanted;             /* has an event handler asked for
                                         * a colour selector? */
                                         * a colour selector? */
@@ -435,56 +451,60 @@ struct dlgparam {
  */
  */
 void ctlposinit(struct ctlpos *cp, HWND hwnd,
 void ctlposinit(struct ctlpos *cp, HWND hwnd,
                 int leftborder, int rightborder, int topborder);
                 int leftborder, int rightborder, int topborder);
-HWND doctl(struct ctlpos *cp, RECT r,
-           char *wclass, int wstyle, int exstyle, char *wtext, int wid);
-void bartitle(struct ctlpos *cp, char *name, int id);
-void beginbox(struct ctlpos *cp, char *name, int idbox);
+HWND doctl(struct ctlpos *cp, RECT r, const char *wclass, int wstyle,
+           int exstyle, const char *wtext, int wid);
+void bartitle(struct ctlpos *cp, const char *name, int id);
+void beginbox(struct ctlpos *cp, const char *name, int idbox);
 void endbox(struct ctlpos *cp);
 void endbox(struct ctlpos *cp);
-void editboxfw(struct ctlpos *cp, bool password, char *text,
-               int staticid, int editid);
-void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...);
+void editboxfw(struct ctlpos *cp, bool password, bool readonly,
+               const char *text, int staticid, int editid);
+void radioline(struct ctlpos *cp, const char *text, int id, int nacross, ...);
 void bareradioline(struct ctlpos *cp, int nacross, ...);
 void bareradioline(struct ctlpos *cp, int nacross, ...);
-void radiobig(struct ctlpos *cp, char *text, int id, ...);
-void checkbox(struct ctlpos *cp, char *text, int id);
-void statictext(struct ctlpos *cp, char *text, int lines, int id);
-void staticbtn(struct ctlpos *cp, char *stext, int sid,
-               char *btext, int bid);
-void static2btn(struct ctlpos *cp, char *stext, int sid,
-                char *btext1, int bid1, char *btext2, int bid2);
-void staticedit(struct ctlpos *cp, char *stext,
+void radiobig(struct ctlpos *cp, const char *text, int id, ...);
+void checkbox(struct ctlpos *cp, const char *text, int id);
+void button(struct ctlpos *cp, const char *btext, int bid, bool defbtn);
+void statictext(struct ctlpos *cp, const char *text, int lines, int id);
+void staticbtn(struct ctlpos *cp, const char *stext, int sid,
+               const char *btext, int bid);
+void static2btn(struct ctlpos *cp, const char *stext, int sid,
+                const char *btext1, int bid1, const char *btext2, int bid2);
+void staticedit(struct ctlpos *cp, const char *stext,
                 int sid, int eid, int percentedit);
                 int sid, int eid, int percentedit);
-void staticddl(struct ctlpos *cp, char *stext,
+void staticddl(struct ctlpos *cp, const char *stext,
                int sid, int lid, int percentlist);
                int sid, int lid, int percentlist);
-void combobox(struct ctlpos *cp, char *text, int staticid, int listid);
-void staticpassedit(struct ctlpos *cp, char *stext,
+void combobox(struct ctlpos *cp, const char *text, int staticid, int listid);
+void staticpassedit(struct ctlpos *cp, const char *stext,
                     int sid, int eid, int percentedit);
                     int sid, int eid, int percentedit);
-void bigeditctrl(struct ctlpos *cp, char *stext,
+void bigeditctrl(struct ctlpos *cp, const char *stext,
                  int sid, int eid, int lines);
                  int sid, int eid, int lines);
-void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id);
-void editbutton(struct ctlpos *cp, char *stext, int sid,
-                int eid, char *btext, int bid);
-void sesssaver(struct ctlpos *cp, char *text,
+void ersatztab(struct ctlpos *cp, const char *stext, int sid, int lid,
+               int s2id);
+void editbutton(struct ctlpos *cp, const char *stext, int sid,
+                int eid, const char *btext, int bid);
+void sesssaver(struct ctlpos *cp, const char *text,
                int staticid, int editid, int listid, ...);
                int staticid, int editid, int listid, ...);
-void envsetter(struct ctlpos *cp, char *stext, int sid,
-               char *e1stext, int e1sid, int e1id,
-               char *e2stext, int e2sid, int e2id,
-               int listid, char *b1text, int b1id, char *b2text, int b2id);
-void charclass(struct ctlpos *cp, char *stext, int sid, int listid,
-               char *btext, int bid, int eid, char *s2text, int s2id);
-void colouredit(struct ctlpos *cp, char *stext, int sid, int listid,
-                char *btext, int bid, ...);
+void envsetter(struct ctlpos *cp, const char *stext, int sid,
+               const char *e1stext, int e1sid, int e1id,
+               const char *e2stext, int e2sid, int e2id,
+               int listid, const char *b1text, int b1id,
+               const char *b2text, int b2id);
+void charclass(struct ctlpos *cp, const char *stext, int sid, int listid,
+               const char *btext, int bid, int eid, const char *s2text,
+               int s2id);
+void colouredit(struct ctlpos *cp, const char *stext, int sid, int listid,
+                const char *btext, int bid, ...);
 void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines,
 void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines,
-               char *stext, int sid, int listid, int upbid, int dnbid);
+               const char *stext, int sid, int listid, int upbid, int dnbid);
 int handle_prefslist(struct prefslist *hdl,
 int handle_prefslist(struct prefslist *hdl,
                      int *array, int maxmemb,
                      int *array, int maxmemb,
                      bool is_dlmsg, HWND hwnd,
                      bool is_dlmsg, HWND hwnd,
                      WPARAM wParam, LPARAM lParam);
                      WPARAM wParam, LPARAM lParam);
 void progressbar(struct ctlpos *cp, int id);
 void progressbar(struct ctlpos *cp, int id);
-void fwdsetter(struct ctlpos *cp, int listid, char *stext, int sid,
-               char *e1stext, int e1sid, int e1id,
-               char *e2stext, int e2sid, int e2id,
-               char *btext, int bid,
-               char *r1text, int r1id, char *r2text, int r2id);
+void fwdsetter(struct ctlpos *cp, int listid, const char *stext, int sid,
+               const char *e1stext, int e1sid, int e1id,
+               const char *e2stext, int e2sid, int e2id,
+               const char *btext, int bid,
+               const char *r1text, int r1id, const char *r2text, int r2id);
 
 
 void dlg_auto_set_fixed_pitch_flag(dlgparam *dlg);
 void dlg_auto_set_fixed_pitch_flag(dlgparam *dlg);
 bool dlg_get_fixed_pitch_flag(dlgparam *dlg);
 bool dlg_get_fixed_pitch_flag(dlgparam *dlg);
@@ -493,11 +513,11 @@ void dlg_set_fixed_pitch_flag(dlgparam *dlg, bool flag);
 #define MAX_SHORTCUTS_PER_CTRL 16
 #define MAX_SHORTCUTS_PER_CTRL 16
 
 
 /*
 /*
- * This structure is what's stored for each `union control' in the
+ * This structure is what's stored for each `dlgcontrol' in the
  * portable-dialog interface.
  * portable-dialog interface.
  */
  */
 struct winctrl {
 struct winctrl {
-    union control *ctrl;
+    dlgcontrol *ctrl;
     /*
     /*
      * The control may have several components at the Windows
      * The control may have several components at the Windows
      * level, with different dialog IDs. To avoid needing N
      * level, with different dialog IDs. To avoid needing N
@@ -528,7 +548,7 @@ struct winctrl {
 };
 };
 /*
 /*
  * And this structure holds a set of the above, in two separate
  * And this structure holds a set of the above, in two separate
- * tree234s so that it can find an item by `union control' or by
+ * tree234s so that it can find an item by `dlgcontrol' or by
  * dialog ID.
  * dialog ID.
  */
  */
 struct winctrls {
 struct winctrls {
@@ -541,7 +561,7 @@ void winctrl_init(struct winctrls *);
 void winctrl_cleanup(struct winctrls *);
 void winctrl_cleanup(struct winctrls *);
 void winctrl_add(struct winctrls *, struct winctrl *);
 void winctrl_add(struct winctrls *, struct winctrl *);
 void winctrl_remove(struct winctrls *, struct winctrl *);
 void winctrl_remove(struct winctrls *, struct winctrl *);
-struct winctrl *winctrl_findbyctrl(struct winctrls *, union control *);
+struct winctrl *winctrl_findbyctrl(struct winctrls *, dlgcontrol *);
 struct winctrl *winctrl_findbyid(struct winctrls *, int);
 struct winctrl *winctrl_findbyid(struct winctrls *, int);
 struct winctrl *winctrl_findbyindex(struct winctrls *, int);
 struct winctrl *winctrl_findbyindex(struct winctrls *, int);
 void winctrl_layout(struct dlgparam *dp, struct winctrls *wc,
 void winctrl_layout(struct dlgparam *dp, struct winctrls *wc,
@@ -723,7 +743,20 @@ char *get_jumplist_registry_entries(void);
 #define CLIPUI_DEFAULT_INS CLIPUI_EXPLICIT
 #define CLIPUI_DEFAULT_INS CLIPUI_EXPLICIT
 
 
 /* In utils */
 /* In utils */
-char *registry_get_string(HKEY root, const char *path, const char *leaf);
+HKEY open_regkey_fn(bool create, HKEY base, const char *path, ...);
+#define open_regkey(create, base, ...) \
+    open_regkey_fn(create, base, __VA_ARGS__, (const char *)NULL)
+void close_regkey(HKEY key);
+void del_regkey(HKEY key, const char *name);
+char *enum_regkey(HKEY key, int index);
+bool get_reg_dword(HKEY key, const char *name, DWORD *out);
+bool put_reg_dword(HKEY key, const char *name, DWORD value);
+char *get_reg_sz(HKEY key, const char *name);
+bool put_reg_sz(HKEY key, const char *name, const char *str);
+strbuf *get_reg_multi_sz(HKEY key, const char *name);
+bool put_reg_multi_sz(HKEY key, const char *name, strbuf *str);
+
+char *get_reg_sz_simple(HKEY key, const char *name, const char *leaf);
 
 
 /* In cliloop.c */
 /* In cliloop.c */
 typedef bool (*cliloop_pre_t)(void *vctx, const HANDLE **extra_handles,
 typedef bool (*cliloop_pre_t)(void *vctx, const HANDLE **extra_handles,

+ 268 - 290
source/putty/windows/storage.c

@@ -35,6 +35,7 @@
 static const char *const reg_jumplist_key = PUTTY_REG_POS "\\Jumplist";
 static const char *const reg_jumplist_key = PUTTY_REG_POS "\\Jumplist";
 static const char *const reg_jumplist_value = "Recent sessions";
 static const char *const reg_jumplist_value = "Recent sessions";
 static const char *const puttystr = PUTTY_REG_POS "\\Sessions";
 static const char *const puttystr = PUTTY_REG_POS "\\Sessions";
+static const char *const host_ca_key = PUTTY_REG_POS "\\SshHostCAs";
 
 
 static bool tried_shgetfolderpath = false;
 static bool tried_shgetfolderpath = false;
 static HMODULE shell32_module = NULL;
 static HMODULE shell32_module = NULL;
@@ -47,28 +48,16 @@ struct settings_w {
 
 
 settings_w *open_settings_w(const char *sessionname, char **errmsg)
 settings_w *open_settings_w(const char *sessionname, char **errmsg)
 {
 {
-    HKEY subkey1, sesskey;
-    int ret;
-    strbuf *sb;
-
     *errmsg = NULL;
     *errmsg = NULL;
 
 
     if (!sessionname || !*sessionname)
     if (!sessionname || !*sessionname)
         sessionname = "Default Settings";
         sessionname = "Default Settings";
 
 
-    sb = strbuf_new();
+    strbuf *sb = strbuf_new();
     escape_registry_key(sessionname, sb);
     escape_registry_key(sessionname, sb);
 
 
-    ret = RegCreateKey(HKEY_CURRENT_USER, puttystr, &subkey1);
-    if (ret != ERROR_SUCCESS) {
-        strbuf_free(sb);
-        *errmsg = dupprintf("Unable to create registry key\n"
-                            "HKEY_CURRENT_USER\\%s", puttystr);
-        return NULL;
-    }
-    ret = RegCreateKey(subkey1, sb->s, &sesskey);
-    RegCloseKey(subkey1);
-    if (ret != ERROR_SUCCESS) {
+    HKEY sesskey = open_regkey(true, HKEY_CURRENT_USER, puttystr, sb->s);
+    if (!sesskey) {
         *errmsg = dupprintf("Unable to create registry key\n"
         *errmsg = dupprintf("Unable to create registry key\n"
                             "HKEY_CURRENT_USER\\%s\\%s", puttystr, sb->s);
                             "HKEY_CURRENT_USER\\%s\\%s", puttystr, sb->s);
         strbuf_free(sb);
         strbuf_free(sb);
@@ -86,20 +75,18 @@ settings_w *open_settings_w(const char *sessionname, char **errmsg)
 void write_setting_s(settings_w *handle, const char *key, const char *value)
 void write_setting_s(settings_w *handle, const char *key, const char *value)
 {
 {
     if (handle)
     if (handle)
-        RegSetValueEx(handle->sesskey, key, 0, REG_SZ, (CONST BYTE *)value,
-                      1 + strlen(value));
+        put_reg_sz(handle->sesskey, key, value);
 }
 }
 
 
 void write_setting_i(settings_w *handle, const char *key, int value)
 void write_setting_i(settings_w *handle, const char *key, int value)
 {
 {
     if (handle)
     if (handle)
-        RegSetValueEx(handle->sesskey, key, 0, REG_DWORD,
-                      (CONST BYTE *) &value, sizeof(value));
+        put_reg_dword(handle->sesskey, key, value);
 }
 }
 
 
 void close_settings_w(settings_w *handle)
 void close_settings_w(settings_w *handle)
 {
 {
-    RegCloseKey(handle->sesskey);
+    close_regkey(handle->sesskey);
     sfree(handle);
     sfree(handle);
 }
 }
 
 
@@ -109,24 +96,12 @@ struct settings_r {
 
 
 settings_r *open_settings_r(const char *sessionname)
 settings_r *open_settings_r(const char *sessionname)
 {
 {
-    HKEY subkey1, sesskey;
-    strbuf *sb;
-
     if (!sessionname || !*sessionname)
     if (!sessionname || !*sessionname)
         sessionname = "Default Settings";
         sessionname = "Default Settings";
 
 
-    sb = strbuf_new();
+    strbuf *sb = strbuf_new();
     escape_registry_key(sessionname, sb);
     escape_registry_key(sessionname, sb);
-
-    if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) {
-        sesskey = NULL;
-    } else {
-        if (RegOpenKey(subkey1, sb->s, &sesskey) != ERROR_SUCCESS) {
-            sesskey = NULL;
-        }
-        RegCloseKey(subkey1);
-    }
-
+    HKEY sesskey = open_regkey(false, HKEY_CURRENT_USER, puttystr, sb->s);
     strbuf_free(sb);
     strbuf_free(sb);
 
 
     if (!sesskey)
     if (!sesskey)
@@ -141,42 +116,15 @@ settings_r *open_settings_r(const char *sessionname)
 
 
 char *read_setting_s(settings_r *handle, const char *key)
 char *read_setting_s(settings_r *handle, const char *key)
 {
 {
-    DWORD type, allocsize, size;
-    char *ret;
-
     if (!handle)
     if (!handle)
         return NULL;
         return NULL;
-
-    /* Find out the type and size of the data. */
-    if (RegQueryValueEx(handle->sesskey, key, 0,
-                        &type, NULL, &size) != ERROR_SUCCESS ||
-        type != REG_SZ)
-        return NULL;
-
-    allocsize = size+1;         /* allow for an extra NUL if needed */
-    ret = snewn(allocsize, char);
-    if (RegQueryValueEx(handle->sesskey, key, 0,
-                        &type, (BYTE *)ret, &size) != ERROR_SUCCESS ||
-        type != REG_SZ) {
-        sfree(ret);
-        return NULL;
-    }
-    assert(size < allocsize);
-    ret[size] = '\0'; /* add an extra NUL in case RegQueryValueEx
-                       * didn't supply one */
-
-    return ret;
+    return get_reg_sz(handle->sesskey, key);
 }
 }
 
 
 int read_setting_i(settings_r *handle, const char *key, int defvalue)
 int read_setting_i(settings_r *handle, const char *key, int defvalue)
 {
 {
-    DWORD type, val, size;
-    size = sizeof(val);
-
-    if (!handle ||
-        RegQueryValueEx(handle->sesskey, key, 0, &type,
-                        (BYTE *) &val, &size) != ERROR_SUCCESS ||
-        size != sizeof(val) || type != REG_DWORD)
+    DWORD val;
+    if (!handle || !get_reg_dword(handle->sesskey, key, &val))
         return defvalue;
         return defvalue;
     else
     else
         return val;
         return val;
@@ -259,25 +207,23 @@ void write_setting_filename(settings_w *handle,
 void close_settings_r(settings_r *handle)
 void close_settings_r(settings_r *handle)
 {
 {
     if (handle) {
     if (handle) {
-        RegCloseKey(handle->sesskey);
+        close_regkey(handle->sesskey);
         sfree(handle);
         sfree(handle);
     }
     }
 }
 }
 
 
 void del_settings(const char *sessionname)
 void del_settings(const char *sessionname)
 {
 {
-    HKEY subkey1;
-    strbuf *sb;
-
-    if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS)
+    HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, puttystr);
+    if (!rkey)
         return;
         return;
 
 
-    sb = strbuf_new();
+    strbuf *sb = strbuf_new();
     escape_registry_key(sessionname, sb);
     escape_registry_key(sessionname, sb);
-    RegDeleteKey(subkey1, sb->s);
+    del_regkey(rkey, sb->s);
     strbuf_free(sb);
     strbuf_free(sb);
 
 
-    RegCloseKey(subkey1);
+    close_regkey(rkey);
 
 
     remove_session_from_jumplist(sessionname);
     remove_session_from_jumplist(sessionname);
 }
 }
@@ -289,13 +235,11 @@ struct settings_e {
 
 
 settings_e *enum_settings_start(void)
 settings_e *enum_settings_start(void)
 {
 {
-    settings_e *ret;
-    HKEY key;
-
-    if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &key) != ERROR_SUCCESS)
+    HKEY key = open_regkey(false, HKEY_CURRENT_USER, puttystr);
+    if (!key)
         return NULL;
         return NULL;
 
 
-    ret = snew(settings_e);
+    settings_e *ret = snew(settings_e);
     if (ret) {
     if (ret) {
         ret->key = key;
         ret->key = key;
         ret->i = 0;
         ret->i = 0;
@@ -306,30 +250,19 @@ settings_e *enum_settings_start(void)
 
 
 bool enum_settings_next(settings_e *e, strbuf *sb)
 bool enum_settings_next(settings_e *e, strbuf *sb)
 {
 {
-    size_t regbuf_size = MAX_PATH + 1;
-    char *regbuf = snewn(regbuf_size, char);
-    bool success;
-
-    while (1) {
-        DWORD retd = RegEnumKey(e->key, e->i, regbuf, regbuf_size);
-        if (retd != ERROR_MORE_DATA) {
-            success = (retd == ERROR_SUCCESS);
-            break;
-        }
-        sgrowarray(regbuf, regbuf_size, regbuf_size);
-    }
-
-    if (success)
-        unescape_registry_key(regbuf, sb);
+    char *name = enum_regkey(e->key, e->i);
+    if (!name)
+        return false;
 
 
+    unescape_registry_key(name, sb);
+    sfree(name);
     e->i++;
     e->i++;
-    sfree(regbuf);
-    return success;
+    return true;
 }
 }
 
 
 void enum_settings_finish(settings_e *e)
 void enum_settings_finish(settings_e *e)
 {
 {
-    RegCloseKey(e->key);
+    close_regkey(e->key);
     sfree(e);
     sfree(e);
 }
 }
 
 
@@ -345,55 +278,37 @@ int retrieve_host_key(const char *hostname, int port,
                     const char *keytype, char *key, int maxlen)
                     const char *keytype, char *key, int maxlen)
 #else
 #else
 int check_stored_host_key(const char *hostname, int port,
 int check_stored_host_key(const char *hostname, int port,
-                    const char *keytype, const char *key)
+                          const char *keytype, const char *key)
 #endif
 #endif
 {
 {
-    char *otherstr;
-    strbuf *regname;
-    int len;
-    HKEY rkey;
-    DWORD readlen;
-    DWORD type;
-    int ret, compare;
-
 #ifdef MPEXT
 #ifdef MPEXT
     len = maxlen;
     len = maxlen;
 #else
 #else
-    len = 1 + strlen(key);
 #endif
 #endif
-
     /*
     /*
-     * Now read a saved key in from the registry and see what it
-     * says.
+     * Read a saved key in from the registry and see what it says.
      */
      */
-    regname = strbuf_new();
+    strbuf *regname = strbuf_new();
     hostkey_regname(regname, hostname, port, keytype);
     hostkey_regname(regname, hostname, port, keytype);
 
 
-    if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys",
-                   &rkey) != ERROR_SUCCESS) {
+    HKEY rkey = open_regkey(false, HKEY_CURRENT_USER,
+                            PUTTY_REG_POS "\\SshHostKeys");
+    if (!rkey) {
         strbuf_free(regname);
         strbuf_free(regname);
         return 1;                      /* key does not exist in registry */
         return 1;                      /* key does not exist in registry */
     }
     }
 
 
-    readlen = len;
-    otherstr = snewn(len, char);
-    ret = RegQueryValueEx(rkey, regname->s, NULL,
-                          &type, (BYTE *)otherstr, &readlen);
-
-    if (ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA &&
-        !strcmp(keytype, "rsa")) {
+    char *otherstr = get_reg_sz(rkey, regname->s);
+    if (!otherstr && !strcmp(keytype, "rsa")) {
         /*
         /*
          * Key didn't exist. If the key type is RSA, we'll try
          * Key didn't exist. If the key type is RSA, we'll try
          * another trick, which is to look up the _old_ key format
          * another trick, which is to look up the _old_ key format
          * under just the hostname and translate that.
          * under just the hostname and translate that.
          */
          */
         char *justhost = regname->s + 1 + strcspn(regname->s, ":");
         char *justhost = regname->s + 1 + strcspn(regname->s, ":");
-        char *oldstyle = snewn(len + 10, char); /* safety margin */
-        readlen = len;
-        ret = RegQueryValueEx(rkey, justhost, NULL, &type,
-                              (BYTE *)oldstyle, &readlen);
+        char *oldstyle = get_reg_sz(rkey, justhost);
 
 
-        if (ret == ERROR_SUCCESS && type == REG_SZ) {
+        if (oldstyle) {
             /*
             /*
              * The old format is two old-style bignums separated by
              * The old format is two old-style bignums separated by
              * a slash. An old-style bignum is made of groups of
              * a slash. An old-style bignum is made of groups of
@@ -406,29 +321,26 @@ int check_stored_host_key(const char *hostname, int port,
              * doesn't appear anyway in RSA keys) separated by a
              * doesn't appear anyway in RSA keys) separated by a
              * comma. All hex digits are lowercase in both formats.
              * comma. All hex digits are lowercase in both formats.
              */
              */
-            char *p = otherstr;
-            char *q = oldstyle;
+            strbuf *new = strbuf_new();
+            const char *q = oldstyle;
             int i, j;
             int i, j;
 
 
             for (i = 0; i < 2; i++) {
             for (i = 0; i < 2; i++) {
                 int ndigits, nwords;
                 int ndigits, nwords;
-                *p++ = '0';
-                *p++ = 'x';
+                put_datapl(new, PTRLEN_LITERAL("0x"));
                 ndigits = strcspn(q, "/");      /* find / or end of string */
                 ndigits = strcspn(q, "/");      /* find / or end of string */
                 nwords = ndigits / 4;
                 nwords = ndigits / 4;
                 /* now trim ndigits to remove leading zeros */
                 /* now trim ndigits to remove leading zeros */
                 while (q[(ndigits - 1) ^ 3] == '0' && ndigits > 1)
                 while (q[(ndigits - 1) ^ 3] == '0' && ndigits > 1)
                     ndigits--;
                     ndigits--;
                 /* now move digits over to new string */
                 /* now move digits over to new string */
-                for (j = 0; j < ndigits; j++)
-                    p[ndigits - 1 - j] = q[j ^ 3];
-                p += ndigits;
+                for (j = ndigits; j-- > 0 ;)
+                    put_byte(new, q[j ^ 3]);
                 q += nwords * 4;
                 q += nwords * 4;
                 if (*q) {
                 if (*q) {
-                    q++;               /* eat the slash */
-                    *p++ = ',';        /* add a comma */
+                    q++;                 /* eat the slash */
+                    put_byte(new, ',');  /* add a comma */
                 }
                 }
-                *p = '\0';             /* terminate the string */
             }
             }
 
 
             /*
             /*
@@ -436,15 +348,18 @@ int check_stored_host_key(const char *hostname, int port,
              * format. If not, we'll assume something odd went
              * format. If not, we'll assume something odd went
              * wrong, and hyper-cautiously do nothing.
              * wrong, and hyper-cautiously do nothing.
              */
              */
-            if (!strcmp(otherstr, key))
-                RegSetValueEx(rkey, regname->s, 0, REG_SZ, (BYTE *)otherstr,
-                              strlen(otherstr) + 1);
+            if (!strcmp(new->s, key)) {
+                put_reg_sz(rkey, regname->s, new->s);
+                otherstr = strbuf_to_str(new);
+            } else {
+                strbuf_free(new);
+            }
         }
         }
 
 
         sfree(oldstyle);
         sfree(oldstyle);
     }
     }
 
 
-    RegCloseKey(rkey);
+    close_regkey(rkey);
 
 
 #ifdef MPEXT
 #ifdef MPEXT
     // make sure it is zero terminated, what it is not, particularly when
     // make sure it is zero terminated, what it is not, particularly when
@@ -455,27 +370,25 @@ int check_stored_host_key(const char *hostname, int port,
     strncpy(key, otherstr, maxlen);
     strncpy(key, otherstr, maxlen);
     key[maxlen - 1] = '\0';
     key[maxlen - 1] = '\0';
 #else
 #else
-    compare = strcmp(otherstr, key);
+    int compare = otherstr ? strcmp(otherstr, key) : -1;
 #endif
 #endif
 
 
     sfree(otherstr);
     sfree(otherstr);
     strbuf_free(regname);
     strbuf_free(regname);
 
 
-#ifndef MPEXT
-    if (ret == ERROR_MORE_DATA ||
-        (ret == ERROR_SUCCESS && type == REG_SZ && compare))
+    if (!otherstr)
+        return 1;                      /* key does not exist in registry */
+#ifndef WINSCP
+    else if (compare)
         return 2;                      /* key is different in registry */
         return 2;                      /* key is different in registry */
-    else
 #endif
 #endif
-    if (ret != ERROR_SUCCESS || type != REG_SZ)
-        return 1;                      /* key does not exist in registry */
     else
     else
         return 0;                      /* key matched OK in registry */
         return 0;                      /* key matched OK in registry */
 }
 }
 
 
 #ifndef MPEXT
 #ifndef MPEXT
 bool have_ssh_host_key(const char *hostname, int port,
 bool have_ssh_host_key(const char *hostname, int port,
-                      const char *keytype)
+                       const char *keytype)
 {
 {
     /*
     /*
      * If we have a host key, check_stored_host_key will return 0 or 2.
      * If we have a host key, check_stored_host_key will return 0 or 2.
@@ -488,22 +401,153 @@ bool have_ssh_host_key(const char *hostname, int port,
 void store_host_key(const char *hostname, int port,
 void store_host_key(const char *hostname, int port,
                     const char *keytype, const char *key)
                     const char *keytype, const char *key)
 {
 {
-    strbuf *regname;
-    HKEY rkey;
-
-    regname = strbuf_new();
+    strbuf *regname = strbuf_new();
     hostkey_regname(regname, hostname, port, keytype);
     hostkey_regname(regname, hostname, port, keytype);
 
 
-    if (RegCreateKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys",
-                     &rkey) == ERROR_SUCCESS) {
-        RegSetValueEx(rkey, regname->s, 0, REG_SZ,
-                      (BYTE *)key, strlen(key) + 1);
-        RegCloseKey(rkey);
+    HKEY rkey = open_regkey(true, HKEY_CURRENT_USER,
+                            PUTTY_REG_POS "\\SshHostKeys");
+    if (rkey) {
+        put_reg_sz(rkey, regname->s, key);
+        close_regkey(rkey);
     } /* else key does not exist in registry */
     } /* else key does not exist in registry */
 
 
     strbuf_free(regname);
     strbuf_free(regname);
 }
 }
 
 
+struct host_ca_enum {
+    HKEY key;
+    int i;
+};
+
+host_ca_enum *enum_host_ca_start(void)
+{
+    host_ca_enum *e;
+    HKEY key;
+
+    if (!(key = open_regkey(false, HKEY_CURRENT_USER, host_ca_key)))
+        return NULL;
+
+    e = snew(host_ca_enum);
+    e->key = key;
+    e->i = 0;
+
+    return e;
+}
+
+bool enum_host_ca_next(host_ca_enum *e, strbuf *sb)
+{
+    char *regbuf = enum_regkey(e->key, e->i);
+    if (!regbuf)
+        return false;
+
+    unescape_registry_key(regbuf, sb);
+    sfree(regbuf);
+    e->i++;
+    return true;
+}
+
+void enum_host_ca_finish(host_ca_enum *e)
+{
+    close_regkey(e->key);
+    sfree(e);
+}
+
+host_ca *host_ca_load(const char *name)
+{
+    strbuf *sb;
+    const char *s;
+
+    sb = strbuf_new();
+    escape_registry_key(name, sb);
+    HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, host_ca_key, sb->s);
+    strbuf_free(sb);
+
+    if (!rkey)
+        return NULL;
+
+    host_ca *hca = host_ca_new();
+    hca->name = dupstr(name);
+
+    DWORD val;
+
+    if ((s = get_reg_sz(rkey, "PublicKey")) != NULL)
+        hca->ca_public_key = base64_decode_sb(ptrlen_from_asciz(s));
+
+    if ((s = get_reg_sz(rkey, "Validity")) != NULL) {
+        hca->validity_expression = strbuf_to_str(
+            percent_decode_sb(ptrlen_from_asciz(s)));
+    } else if ((sb = get_reg_multi_sz(rkey, "MatchHosts")) != NULL) {
+        BinarySource src[1];
+        BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(sb));
+        CertExprBuilder *eb = cert_expr_builder_new();
+
+        const char *wc;
+        while (wc = get_asciz(src), !get_err(src))
+            cert_expr_builder_add(eb, wc);
+
+        hca->validity_expression = cert_expr_expression(eb);
+        cert_expr_builder_free(eb);
+    }
+
+    if (get_reg_dword(rkey, "PermitRSASHA1", &val))
+        hca->opts.permit_rsa_sha1 = val;
+    if (get_reg_dword(rkey, "PermitRSASHA256", &val))
+        hca->opts.permit_rsa_sha256 = val;
+    if (get_reg_dword(rkey, "PermitRSASHA512", &val))
+        hca->opts.permit_rsa_sha512 = val;
+
+    close_regkey(rkey);
+    return hca;
+}
+
+char *host_ca_save(host_ca *hca)
+{
+    if (!*hca->name)
+        return dupstr("CA record must have a name");
+
+    strbuf *sb = strbuf_new();
+    escape_registry_key(hca->name, sb);
+    HKEY rkey = open_regkey(true, HKEY_CURRENT_USER, host_ca_key, sb->s);
+    if (!rkey) {
+        char *err = dupprintf("Unable to create registry key\n"
+                              "HKEY_CURRENT_USER\\%s\\%s", host_ca_key, sb->s);
+        strbuf_free(sb);
+        return err;
+    }
+    strbuf_free(sb);
+
+    strbuf *base64_pubkey = base64_encode_sb(
+        ptrlen_from_strbuf(hca->ca_public_key), 0);
+    put_reg_sz(rkey, "PublicKey", base64_pubkey->s);
+    strbuf_free(base64_pubkey);
+
+    strbuf *validity = percent_encode_sb(
+        ptrlen_from_asciz(hca->validity_expression), NULL);
+    put_reg_sz(rkey, "Validity", validity->s);
+    strbuf_free(validity);
+
+    put_reg_dword(rkey, "PermitRSASHA1", hca->opts.permit_rsa_sha1);
+    put_reg_dword(rkey, "PermitRSASHA256", hca->opts.permit_rsa_sha256);
+    put_reg_dword(rkey, "PermitRSASHA512", hca->opts.permit_rsa_sha512);
+
+    close_regkey(rkey);
+    return NULL;
+}
+
+char *host_ca_delete(const char *name)
+{
+    HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, host_ca_key);
+    if (!rkey)
+        return NULL;
+
+    strbuf *sb = strbuf_new();
+    escape_registry_key(name, sb);
+    del_regkey(rkey, sb->s);
+    strbuf_free(sb);
+
+    return NULL;
+}
+
 /*
 /*
  * Open (or delete) the random seed file.
  * Open (or delete) the random seed file.
  */
  */
@@ -540,7 +584,6 @@ static bool try_random_seed_and_free(char *path, int action, HANDLE *hout)
 
 
 static HANDLE access_random_seed(int action)
 static HANDLE access_random_seed(int action)
 {
 {
-    HKEY rkey;
     HANDLE rethandle;
     HANDLE rethandle;
 
 
     /*
     /*
@@ -559,16 +602,16 @@ static HANDLE access_random_seed(int action)
      * Registry, if any.
      * Registry, if any.
      */
      */
     {
     {
-        char regpath[MAX_PATH + 1];
-        DWORD type, size = sizeof(regpath);
-        if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &rkey) ==
-            ERROR_SUCCESS) {
-            int ret = RegQueryValueEx(rkey, "RandSeedFile",
-                                      0, &type, (BYTE *)regpath, &size);
-            RegCloseKey(rkey);
-            if (ret == ERROR_SUCCESS && type == REG_SZ &&
-                try_random_seed(regpath, action, &rethandle))
-                return rethandle;
+        HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, PUTTY_REG_POS);
+        if (rkey) {
+            char *regpath = get_reg_sz(rkey, "RandSeedFile");
+            close_regkey(rkey);
+            if (regpath) {
+                bool success = try_random_seed(regpath, action, &rethandle);
+                sfree(regpath);
+                if (success)
+                    return rethandle;
+            }
         }
         }
     }
     }
 
 
@@ -683,132 +726,69 @@ void write_random_seed(void *data, int len)
  * returning the resulting concatenated list of strings in 'out' (if
  * returning the resulting concatenated list of strings in 'out' (if
  * non-null).
  * non-null).
  */
  */
-static int transform_jumplist_registry
-    (const char *add, const char *rem, char **out)
-{
-    int ret;
-    HKEY pjumplist_key;
-    DWORD type;
-    DWORD value_length;
-    char *old_value, *new_value;
-    char *piterator_old, *piterator_new, *piterator_tmp;
-
-    ret = RegCreateKeyEx(HKEY_CURRENT_USER, reg_jumplist_key, 0, NULL,
-                         REG_OPTION_NON_VOLATILE, (KEY_READ | KEY_WRITE), NULL,
-                         &pjumplist_key, NULL);
-    if (ret != ERROR_SUCCESS) {
+static int transform_jumplist_registry(
+    const char *add, const char *rem, char **out)
+{
+    HKEY rkey = open_regkey(true, HKEY_CURRENT_USER, reg_jumplist_key);
+    if (!rkey)
         return JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE;
         return JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE;
-    }
 
 
     /* Get current list of saved sessions in the registry. */
     /* Get current list of saved sessions in the registry. */
-    value_length = 200;
-    old_value = snewn(value_length, char);
-    ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type,
-                          (BYTE *)old_value, &value_length);
-    /* When the passed buffer is too small, ERROR_MORE_DATA is
-     * returned and the required size is returned in the length
-     * argument. */
-    if (ret == ERROR_MORE_DATA) {
-        sfree(old_value);
-        old_value = snewn(value_length, char);
-        ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type,
-                              (BYTE *)old_value, &value_length);
-    }
-
-    if (ret == ERROR_FILE_NOT_FOUND) {
-        /* Value doesn't exist yet. Start from an empty value. */
-        *old_value = '\0';
-        *(old_value + 1) = '\0';
-    } else if (ret != ERROR_SUCCESS) {
-        /* Some non-recoverable error occurred. */
-        sfree(old_value);
-        RegCloseKey(pjumplist_key);
-        return JUMPLISTREG_ERROR_VALUEREAD_FAILURE;
-    } else if (type != REG_MULTI_SZ) {
-        /* The value present in the registry has the wrong type: we
-         * try to delete it and start from an empty value. */
-        ret = RegDeleteValue(pjumplist_key, reg_jumplist_value);
-        if (ret != ERROR_SUCCESS) {
-            sfree(old_value);
-            RegCloseKey(pjumplist_key);
-            return JUMPLISTREG_ERROR_VALUEREAD_FAILURE;
-        }
-
-        *old_value = '\0';
-        *(old_value + 1) = '\0';
-    }
-
-    /* Check validity of registry data: REG_MULTI_SZ value must end
-     * with \0\0. */
-    piterator_tmp = old_value;
-    while (((piterator_tmp - old_value) < (value_length - 1)) &&
-           !(*piterator_tmp == '\0' && *(piterator_tmp+1) == '\0')) {
-        ++piterator_tmp;
-    }
-
-    if ((piterator_tmp - old_value) >= (value_length-1)) {
-        /* Invalid value. Start from an empty value. */
-        *old_value = '\0';
-        *(old_value + 1) = '\0';
+    strbuf *oldlist = get_reg_multi_sz(rkey, reg_jumplist_value);
+    if (!oldlist) {
+        /* Start again with the empty list. */
+        oldlist = strbuf_new();
+        put_data(oldlist, "\0\0", 2);
     }
     }
 
 
     /*
     /*
      * Modify the list, if we're modifying.
      * Modify the list, if we're modifying.
      */
      */
+    bool write_failure = false;
     if (add || rem) {
     if (add || rem) {
-        /* Walk through the existing list and construct the new list of
-         * saved sessions. */
-        new_value = snewn(value_length + (add ? strlen(add) + 1 : 0), char);
-        piterator_new = new_value;
-        piterator_old = old_value;
+        BinarySource src[1];
+        BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(oldlist));
+        strbuf *newlist = strbuf_new();
 
 
         /* First add the new item to the beginning of the list. */
         /* First add the new item to the beginning of the list. */
-        if (add) {
-            strcpy(piterator_new, add);
-            piterator_new += strlen(piterator_new) + 1;
-        }
+        if (add)
+            put_asciz(newlist, add);
+
         /* Now add the existing list, taking care to leave out the removed
         /* Now add the existing list, taking care to leave out the removed
          * item, if it was already in the existing list. */
          * item, if it was already in the existing list. */
-        while (*piterator_old != '\0') {
-            if (!rem || strcmp(piterator_old, rem) != 0) {
+        while (true) {
+            const char *olditem = get_asciz(src);
+            if (get_err(src))
+                break;
+
+            if (!rem || strcmp(olditem, rem) != 0) {
                 /* Check if this is a valid session, otherwise don't add. */
                 /* Check if this is a valid session, otherwise don't add. */
-                settings_r *psettings_tmp = open_settings_r(piterator_old);
+                settings_r *psettings_tmp = open_settings_r(olditem);
                 if (psettings_tmp != NULL) {
                 if (psettings_tmp != NULL) {
                     close_settings_r(psettings_tmp);
                     close_settings_r(psettings_tmp);
-                    strcpy(piterator_new, piterator_old);
-                    piterator_new += strlen(piterator_new) + 1;
+                    put_asciz(newlist, olditem);
                 }
                 }
             }
             }
-            piterator_old += strlen(piterator_old) + 1;
         }
         }
-        *piterator_new = '\0';
-        ++piterator_new;
 
 
         /* Save the new list to the registry. */
         /* Save the new list to the registry. */
-        ret = RegSetValueEx(pjumplist_key, reg_jumplist_value, 0, REG_MULTI_SZ,
-                            (BYTE *)new_value, piterator_new - new_value);
+        write_failure = !put_reg_multi_sz(rkey, reg_jumplist_value, newlist);
 
 
-        sfree(old_value);
-        old_value = new_value;
-    } else
-        ret = ERROR_SUCCESS;
+        strbuf_free(oldlist);
+        oldlist = newlist;
+    }
 
 
-    /*
-     * Either return or free the result.
-     */
-    if (out && ret == ERROR_SUCCESS)
-        *out = old_value;
-    else
-        sfree(old_value);
+    close_regkey(rkey);
 
 
-    /* Clean up and return. */
-    RegCloseKey(pjumplist_key);
+    if (out && !write_failure)
+        *out = strbuf_to_str(oldlist);
+    else
+        strbuf_free(oldlist);
 
 
-    if (ret != ERROR_SUCCESS) {
+    if (write_failure)
         return JUMPLISTREG_ERROR_VALUEWRITE_FAILURE;
         return JUMPLISTREG_ERROR_VALUEWRITE_FAILURE;
-    } else {
+    else
         return JUMPLISTREG_OK;
         return JUMPLISTREG_OK;
-    }
 }
 }
 
 
 /* Adds a new entry to the jumplist entries in the registry. */
 /* Adds a new entry to the jumplist entries in the registry. */
@@ -842,26 +822,22 @@ char *get_jumplist_registry_entries (void)
  */
  */
 static void registry_recursive_remove(HKEY key)
 static void registry_recursive_remove(HKEY key)
 {
 {
-    DWORD i;
-    char name[MAX_PATH + 1];
-    HKEY subkey;
+    char *name;
 
 
-    i = 0;
-    while (RegEnumKey(key, i, name, sizeof(name)) == ERROR_SUCCESS) {
-        if (RegOpenKey(key, name, &subkey) == ERROR_SUCCESS) {
+    DWORD i = 0;
+    while ((name = enum_regkey(key, i)) != NULL) {
+        HKEY subkey = open_regkey(false, key, name);
+        if (subkey) {
             registry_recursive_remove(subkey);
             registry_recursive_remove(subkey);
-            RegCloseKey(subkey);
+            close_regkey(subkey);
         }
         }
-        RegDeleteKey(key, name);
+        del_regkey(key, name);
+        sfree(name);
     }
     }
 }
 }
 
 
 void cleanup_all(void)
 void cleanup_all(void)
 {
 {
-    HKEY key;
-    int ret;
-    char name[MAX_PATH + 1];
-
     /* ------------------------------------------------------------
     /* ------------------------------------------------------------
      * Wipe out the random seed file, in all of its possible
      * Wipe out the random seed file, in all of its possible
      * locations.
      * locations.
@@ -881,31 +857,34 @@ void cleanup_all(void)
     /*
     /*
      * Open the main PuTTY registry key and remove everything in it.
      * Open the main PuTTY registry key and remove everything in it.
      */
      */
-    if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &key) ==
-        ERROR_SUCCESS) {
+    HKEY key = open_regkey(false, HKEY_CURRENT_USER, PUTTY_REG_POS);
+    if (key) {
         registry_recursive_remove(key);
         registry_recursive_remove(key);
-        RegCloseKey(key);
+        close_regkey(key);
     }
     }
     /*
     /*
      * Now open the parent key and remove the PuTTY main key. Once
      * Now open the parent key and remove the PuTTY main key. Once
      * we've done that, see if the parent key has any other
      * we've done that, see if the parent key has any other
      * children.
      * children.
      */
      */
-    if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_PARENT,
-                   &key) == ERROR_SUCCESS) {
-        RegDeleteKey(key, PUTTY_REG_PARENT_CHILD);
-        ret = RegEnumKey(key, 0, name, sizeof(name));
-        RegCloseKey(key);
+    if ((key = open_regkey(false, HKEY_CURRENT_USER,
+                           PUTTY_REG_PARENT)) != NULL) {
+        del_regkey(key, PUTTY_REG_PARENT_CHILD);
+        char *name = enum_regkey(key, 0);
+        close_regkey(key);
+
         /*
         /*
          * If the parent key had no other children, we must delete
          * If the parent key had no other children, we must delete
          * it in its turn. That means opening the _grandparent_
          * it in its turn. That means opening the _grandparent_
          * key.
          * key.
          */
          */
-        if (ret != ERROR_SUCCESS) {
-            if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_GPARENT,
-                           &key) == ERROR_SUCCESS) {
-                RegDeleteKey(key, PUTTY_REG_GPARENT_CHILD);
-                RegCloseKey(key);
+        if (name) {
+            sfree(name);
+        } else {
+            if ((key = open_regkey(false, HKEY_CURRENT_USER,
+                                   PUTTY_REG_GPARENT)) != NULL) {
+                del_regkey(key, PUTTY_REG_GPARENT_CHILD);
+                close_regkey(key);
             }
             }
         }
         }
     }
     }
@@ -913,4 +892,3 @@ void cleanup_all(void)
      * Now we're done.
      * Now we're done.
      */
      */
 }
 }
-

+ 153 - 41
source/putty/windows/unicode.c

@@ -438,11 +438,116 @@ static const struct cp_list_item cp_list[] = {
     {0, 0}
     {0, 0}
 };
 };
 
 
-static void link_font(WCHAR * line_tbl, WCHAR * font_tbl, WCHAR attr);
+static void link_font(WCHAR *line_tbl, WCHAR *font_tbl, WCHAR attr);
+
+/*
+ * We keep a collection of reverse mappings from Unicode back to code pages,
+ * in the form of array[256] of array[256] of char. These live forever in a
+ * local tree234, and we just make a new one whenever we find a need.
+ */
+typedef struct reverse_mapping {
+    int codepage;
+    char **blocks;
+} reverse_mapping;
+static tree234 *reverse_mappings = NULL;
+
+static int reverse_mapping_cmp(void *av, void *bv)
+{
+    const reverse_mapping *a = (const reverse_mapping *)av;
+    const reverse_mapping *b = (const reverse_mapping *)bv;
+    if (a->codepage < b->codepage)
+        return -1;
+    if (a->codepage > b->codepage)
+        return +1;
+    return 0;
+}
+
+static int reverse_mapping_find(void *av, void *bv)
+{
+    const reverse_mapping *a = (const reverse_mapping *)av;
+    int b_codepage = *(const int *)bv;
+    if (a->codepage < b_codepage)
+        return -1;
+    if (a->codepage > b_codepage)
+        return +1;
+    return 0;
+}
+
+static reverse_mapping *get_existing_reverse_mapping(int codepage)
+{
+    if (!reverse_mappings)
+        return NULL;
+    return find234(reverse_mappings, &codepage, reverse_mapping_find);
+}
+
+static reverse_mapping *make_reverse_mapping_inner(
+    int codepage, const wchar_t *mapping)
+{
+    if (!reverse_mappings)
+        reverse_mappings = newtree234(reverse_mapping_cmp);
+
+    reverse_mapping *rmap = snew(reverse_mapping);
+    rmap->blocks = snewn(256, char *);
+    memset(rmap->blocks, 0, 256 * sizeof(char *));
+
+    for (size_t i = 0; i < 256; i++) {
+        /* These special kinds of value correspond to no Unicode character */
+        if (DIRECT_CHAR(mapping[i]))
+            continue;
+        if (DIRECT_FONT(mapping[i]))
+            continue;
+
+        size_t chr = mapping[i];
+        size_t block = chr >> 8, index = chr & 0xFF;
+
+        if (!rmap->blocks[block]) {
+            rmap->blocks[block] = snewn(256, char);
+            memset(rmap->blocks[block], 0, 256);
+        }
+        rmap->blocks[block][index] = i;
+    }
+
+    rmap->codepage = codepage;
+    reverse_mapping *added = add234(reverse_mappings, rmap);
+    assert(added == rmap); /* we already checked it wasn't already in there */
+    return added;
+}
+
+static void make_reverse_mapping(int codepage, const wchar_t *mapping)
+{
+    if (get_existing_reverse_mapping(codepage))
+        return;                        /* we've already got this one */
+    make_reverse_mapping_inner(codepage, mapping);
+}
+
+static reverse_mapping *get_reverse_mapping(int codepage)
+{
+    /*
+     * Try harder to get a reverse mapping for a codepage we implement
+     * internally via a translation table, by hastily making it if it doesn't
+     * already exist.
+     */
+
+    reverse_mapping *rmap = get_existing_reverse_mapping(codepage);
+    if (rmap)
+        return rmap;
+
+    if (codepage < 65536)
+        return NULL;
+    if (codepage >= 65536 + lenof(cp_list))
+        return NULL;
+    const struct cp_list_item *cp = &cp_list[codepage - 65536];
+    if (!cp->cp_table)
+        return NULL;
+
+    wchar_t mapping[256];
+    get_unitab(codepage, mapping, 0);
+    return make_reverse_mapping_inner(codepage, mapping);
+}
 
 
 void init_ucs(Conf *conf, struct unicode_data *ucsdata)
 void init_ucs(Conf *conf, struct unicode_data *ucsdata)
 {
 {
-    int i, j;
+    int i;
     bool used_dtf = false;
     bool used_dtf = false;
     int vtmode;
     int vtmode;
 
 
@@ -525,31 +630,9 @@ void init_ucs(Conf *conf, struct unicode_data *ucsdata)
            sizeof(unitab_xterm_std));
            sizeof(unitab_xterm_std));
     ucsdata->unitab_xterm['_'] = ' ';
     ucsdata->unitab_xterm['_'] = ' ';
 
 
-    /* Generate UCS ->line page table. */
-    if (ucsdata->uni_tbl) {
-        for (i = 0; i < 256; i++)
-            if (ucsdata->uni_tbl[i])
-                sfree(ucsdata->uni_tbl[i]);
-        sfree(ucsdata->uni_tbl);
-        ucsdata->uni_tbl = 0;
-    }
     if (!used_dtf) {
     if (!used_dtf) {
-        for (i = 0; i < 256; i++) {
-            if (DIRECT_CHAR(ucsdata->unitab_line[i]))
-                continue;
-            if (DIRECT_FONT(ucsdata->unitab_line[i]))
-                continue;
-            if (!ucsdata->uni_tbl) {
-                ucsdata->uni_tbl = snewn(256, char *);
-                memset(ucsdata->uni_tbl, 0, 256 * sizeof(char *));
-            }
-            j = ((ucsdata->unitab_line[i] >> 8) & 0xFF);
-            if (!ucsdata->uni_tbl[j]) {
-                ucsdata->uni_tbl[j] = snewn(256, char);
-                memset(ucsdata->uni_tbl[j], 0, 256 * sizeof(char));
-            }
-            ucsdata->uni_tbl[j][ucsdata->unitab_line[i] & 0xFF] = i;
-        }
+        /* Make sure a reverse mapping exists for this code page. */
+        make_reverse_mapping(ucsdata->line_codepage, ucsdata->unitab_line);
     }
     }
 
 
     /* Find the line control characters. */
     /* Find the line control characters. */
@@ -601,7 +684,7 @@ void init_ucs(Conf *conf, struct unicode_data *ucsdata)
         for (i = 96; i < 127; i++)
         for (i = 96; i < 127; i++)
             if (!DIRECT_FONT(ucsdata->unitab_xterm[i]))
             if (!DIRECT_FONT(ucsdata->unitab_xterm[i]))
                 ucsdata->unitab_xterm[i] =
                 ucsdata->unitab_xterm[i] =
-            (WCHAR) (CSET_ACP + poorman_vt100[i - 96]);
+                    (WCHAR) (CSET_ACP + poorman_vt100[i - 96]);
         for(i=128;i<256;i++)
         for(i=128;i<256;i++)
             if (!DIRECT_FONT(ucsdata->unitab_scoacs[i]))
             if (!DIRECT_FONT(ucsdata->unitab_scoacs[i]))
                 ucsdata->unitab_scoacs[i] =
                 ucsdata->unitab_scoacs[i] =
@@ -609,7 +692,7 @@ void init_ucs(Conf *conf, struct unicode_data *ucsdata)
     }
     }
 }
 }
 
 
-static void link_font(WCHAR * line_tbl, WCHAR * font_tbl, WCHAR attr)
+static void link_font(WCHAR *line_tbl, WCHAR *font_tbl, WCHAR attr)
 {
 {
     int font_index, line_index, i;
     int font_index, line_index, i;
     for (line_index = 0; line_index < 256; line_index++) {
     for (line_index = 0; line_index < 256; line_index++) {
@@ -644,11 +727,11 @@ wchar_t xlat_uskbd2cyrllic(int ch)
         0x0440, 0x0448, 0x043e, 0x043b, 0x0434, 0x044c, 0x0442, 0x0449,
         0x0440, 0x0448, 0x043e, 0x043b, 0x0434, 0x044c, 0x0442, 0x0449,
         0x0437, 0x0439, 0x043a, 0x044b, 0x0435, 0x0433, 0x043c, 0x0446,
         0x0437, 0x0439, 0x043a, 0x044b, 0x0435, 0x0433, 0x043c, 0x0446,
         0x0447, 0x043d, 0x044f, 0x0425, 0x0407, 0x042a,    126,    127
         0x0447, 0x043d, 0x044f, 0x0425, 0x0407, 0x042a,    126,    127
-       };
+    };
     return cyrtab[ch&0x7F];
     return cyrtab[ch&0x7F];
 }
 }
 
 
-int check_compose_internal(int first, int second, int recurse)
+static int check_compose_internal(int first, int second, int recurse)
 {
 {
 
 
     static const struct {
     static const struct {
@@ -1003,9 +1086,9 @@ int check_compose(int first, int second)
     return check_compose_internal(first, second, 0);
     return check_compose_internal(first, second, 0);
 }
 }
 
 
-int decode_codepage(char *cp_name)
+int decode_codepage(const char *cp_name)
 {
 {
-    char *s, *d;
+    const char *s, *d;
     const struct cp_list_item *cpi;
     const struct cp_list_item *cpi;
     int codepage = -1;
     int codepage = -1;
     CPINFO cpinfo;
     CPINFO cpinfo;
@@ -1120,7 +1203,7 @@ const char *cp_enumerate(int index)
     return cp_list[index].name;
     return cp_list[index].name;
 }
 }
 
 
-void get_unitab(int codepage, wchar_t * unitab, int ftype)
+void get_unitab(int codepage, wchar_t *unitab, int ftype)
 {
 {
     char tbuf[4];
     char tbuf[4];
     int i, max = 256, flg = MB_ERR_INVALID_CHARS;
     int i, max = 256, flg = MB_ERR_INVALID_CHARS;
@@ -1159,20 +1242,21 @@ void get_unitab(int codepage, wchar_t * unitab, int ftype)
 }
 }
 
 
 int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen,
 int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen,
-             char *mbstr, int mblen, const char *defchr,
-             struct unicode_data *ucsdata)
+             char *mbstr, int mblen, const char *defchr)
 {
 {
-    char *p;
-    int i;
-    if (ucsdata && codepage == ucsdata->line_codepage && ucsdata->uni_tbl) {
+    reverse_mapping *rmap = get_reverse_mapping(codepage);
+
+    if (rmap) {
         /* Do this by array lookup if we can. */
         /* Do this by array lookup if we can. */
         if (wclen < 0) {
         if (wclen < 0) {
             for (wclen = 0; wcstr[wclen++] ;);   /* will include the NUL */
             for (wclen = 0; wcstr[wclen++] ;);   /* will include the NUL */
         }
         }
+        char *p;
+        int i;
         for (p = mbstr, i = 0; i < wclen; i++) {
         for (p = mbstr, i = 0; i < wclen; i++) {
             wchar_t ch = wcstr[i];
             wchar_t ch = wcstr[i];
             int by;
             int by;
-            char *p1;
+            const char *p1;
 
 
             #define WRITECH(chr) do             \
             #define WRITECH(chr) do             \
             {                                   \
             {                                   \
@@ -1180,8 +1264,7 @@ int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen,
                 *p++ = (char)(chr);             \
                 *p++ = (char)(chr);             \
             } while (0)
             } while (0)
 
 
-            if (ucsdata->uni_tbl &&
-                (p1 = ucsdata->uni_tbl[(ch >> 8) & 0xFF]) != NULL &&
+            if ((p1 = rmap->blocks[(ch >> 8) & 0xFF]) != NULL &&
                 (by = p1[ch & 0xFF]) != '\0')
                 (by = p1[ch & 0xFF]) != '\0')
                 WRITECH(by);
                 WRITECH(by);
             else if (ch < 0x80)
             else if (ch < 0x80)
@@ -1245,6 +1328,35 @@ int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen,
 int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen,
 int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen,
              wchar_t *wcstr, int wclen)
              wchar_t *wcstr, int wclen)
 {
 {
+    if (codepage >= 65536) {
+        /* Character set not known to Windows, so we'll have to
+         * translate it ourself */
+        size_t index = codepage - 65536;
+        if (index >= lenof(cp_list))
+            return 0;
+        const struct cp_list_item *cp = &cp_list[index];
+        if (!cp->cp_table)
+            return 0;
+
+        size_t remaining = wclen;
+        wchar_t *p = wcstr;
+        unsigned tablebase = 256 - cp->cp_size;
+
+        while (mblen > 0) {
+            mblen--;
+            unsigned c = 0xFF & *mbstr++;
+            wchar_t wc = (c < tablebase ? c : cp->cp_table[c - tablebase]);
+            if (remaining > 0) {
+                remaining--;
+                *p++ = wc;
+            } else {
+                return p - wcstr;
+            }
+        }
+
+        return p - wcstr;
+    }
+
     int ret = MultiByteToWideChar(codepage, flags, mbstr, mblen, wcstr, wclen);
     int ret = MultiByteToWideChar(codepage, flags, mbstr, mblen, wcstr, wclen);
     if (ret)
     if (ret)
         return ret;
         return ret;

+ 184 - 0
source/putty/windows/utils/registry.c

@@ -0,0 +1,184 @@
+/*
+ * Implement convenience wrappers on the awkward low-level functions
+ * for accessing the Windows registry.
+ */
+
+#include "putty.h"
+
+HKEY open_regkey_fn(bool create, HKEY hk, const char *path, ...)
+{
+    HKEY toret = NULL;
+    bool hk_needs_close = false;
+    va_list ap;
+    va_start(ap, path);
+
+    for (; path; path = va_arg(ap, const char *)) {
+        HKEY hk_sub = NULL;
+
+        LONG status;
+        if (create)
+            status = RegCreateKeyEx(
+                hk, path, 0, NULL, REG_OPTION_NON_VOLATILE,
+                KEY_READ | KEY_WRITE, NULL, &hk_sub, NULL);
+        else
+            status = RegOpenKeyEx(
+                hk, path, 0, KEY_READ | KEY_WRITE, &hk_sub);
+
+        if (status != ERROR_SUCCESS)
+            goto out;
+
+        if (hk_needs_close)
+            RegCloseKey(hk);
+        hk = hk_sub;
+        hk_needs_close = true;
+    }
+
+    toret = hk;
+    hk = NULL;
+    hk_needs_close = false;
+
+  out:
+    va_end(ap);
+    if (hk_needs_close)
+        RegCloseKey(hk);
+    return toret;
+}
+
+void close_regkey(HKEY key)
+{
+    RegCloseKey(key);
+}
+
+void del_regkey(HKEY key, const char *name)
+{
+    RegDeleteKey(key, name);
+}
+
+char *enum_regkey(HKEY key, int index)
+{
+    size_t regbuf_size = MAX_PATH + 1;
+    char *regbuf = snewn(regbuf_size, char);
+
+    while (1) {
+        LONG status = RegEnumKey(key, index, regbuf, regbuf_size);
+        if (status == ERROR_SUCCESS)
+            return regbuf;
+        if (status != ERROR_MORE_DATA) {
+            sfree(regbuf);
+            return NULL;
+        }
+        sgrowarray(regbuf, regbuf_size, regbuf_size);
+    }
+}
+
+bool get_reg_dword(HKEY key, const char *name, DWORD *out)
+{
+    DWORD type, size;
+    size = sizeof(*out);
+
+    if (RegQueryValueEx(key, name, 0, &type,
+                        (BYTE *)out, &size) != ERROR_SUCCESS ||
+        size != sizeof(*out) || type != REG_DWORD)
+        return false;
+    else
+        return true;
+}
+
+bool put_reg_dword(HKEY key, const char *name, DWORD value)
+{
+    return RegSetValueEx(key, name, 0, REG_DWORD, (CONST BYTE *) &value,
+                         sizeof(value)) == ERROR_SUCCESS;
+}
+
+char *get_reg_sz(HKEY key, const char *name)
+{
+    DWORD type, size;
+
+    if (RegQueryValueEx(key, name, 0, &type, NULL,
+                        &size) != ERROR_SUCCESS || type != REG_SZ)
+        return NULL;                   /* not a string */
+
+    size_t allocsize = size+1;         /* allow for an extra NUL if needed */
+    char *toret = snewn(allocsize, char);
+    if (RegQueryValueEx(key, name, 0, &type, (BYTE *)toret,
+                        &size) != ERROR_SUCCESS || type != REG_SZ) {
+        sfree(toret);
+        return NULL;
+    }
+    assert(size < allocsize);
+    toret[size] = '\0'; /* add an extra NUL in case RegQueryValueEx
+                         * didn't supply one */
+
+    return toret;
+}
+
+bool put_reg_sz(HKEY key, const char *name, const char *str)
+{
+    /* You have to store the trailing NUL as well */
+    return RegSetValueEx(key, name, 0, REG_SZ, (CONST BYTE *)str,
+                         1 + strlen(str)) == ERROR_SUCCESS;
+}
+
+/*
+ * REG_MULTI_SZ items are stored as a concatenation of NUL-terminated
+ * strings, terminated in turn with an empty string, i.e. a second
+ * consecutive NUL.
+ *
+ * We represent these in their storage format, as a strbuf - but
+ * *without* the second consecutive NUL.
+ *
+ * So you can build up a new MULTI_SZ value in a strbuf by calling
+ * put_asciz once per output string and then put_reg_multi_sz; and you
+ * can consume one by initialising a BinarySource to the result of
+ * get_reg_multi_sz, and then calling get_asciz on it and assuming
+ * that !get_err(src) means you have a real output string.
+ *
+ * Also, calling strbuf_to_str on one of these will give you back a
+ * bare 'char *' with the same double-NUL termination, to pass back to
+ * a caller.
+ */
+strbuf *get_reg_multi_sz(HKEY key, const char *name)
+{
+    DWORD type, size;
+
+    if (RegQueryValueEx(key, name, 0, &type, NULL,
+                        &size) != ERROR_SUCCESS || type != REG_MULTI_SZ)
+        return NULL;                   /* not a string */
+
+    strbuf *toret = strbuf_new();
+    void *ptr = strbuf_append(toret, (size_t)size + 2);
+    if (RegQueryValueEx(key, name, 0, &type, (BYTE *)ptr,
+                        &size) != ERROR_SUCCESS || type != REG_MULTI_SZ) {
+        strbuf_free(toret);
+        return NULL;
+    }
+    strbuf_shrink_to(toret, size);
+    /* Ensure we end with exactly one \0 */
+    while (strbuf_chomp(toret, '\0'));
+    put_byte(toret, '\0');
+    return toret;
+}
+
+bool put_reg_multi_sz(HKEY key, const char *name, strbuf *str)
+{
+    /*
+     * Of course, to write our string list into the registry, we _do_
+     * have to include both trailing NULs. But this is easy, because a
+     * strbuf is also designed to hold a single string and make it
+     * conveniently accessible in NUL-terminated form, so it stores a
+     * NUL in its buffer just beyond its formal length. So we just
+     * include that extra byte in the data we write.
+     */
+    return RegSetValueEx(key, name, 0, REG_MULTI_SZ, (CONST BYTE *)str->s,
+                         str->len + 1) == ERROR_SUCCESS;
+}
+
+char *get_reg_sz_simple(HKEY key, const char *name, const char *leaf)
+{
+    HKEY subkey = open_regkey(false, key, name);
+    if (!subkey)
+        return NULL;
+    char *toret = get_reg_sz(subkey, leaf);
+    RegCloseKey(subkey);
+    return toret;
+}

+ 6 - 6
source/putty/windows/utils/security_p.c

@@ -166,7 +166,7 @@ static bool getsids(char **error)
 
 
     ret = true;
     ret = true;
 
 
- cleanup:
+  cleanup:
     return ret;
     return ret;
 }
 }
 
 
@@ -186,7 +186,7 @@ bool make_private_security_descriptor(DWORD permissions,
     *error = NULL;
     *error = NULL;
 
 
     if (!getsids(error))
     if (!getsids(error))
-      goto cleanup;
+        goto cleanup;
 
 
     memset(ea, 0, sizeof(ea));
     memset(ea, 0, sizeof(ea));
     ea[0].grfAccessPermissions = permissions;
     ea[0].grfAccessPermissions = permissions;
@@ -301,10 +301,10 @@ static bool really_restrict_process_acl(char **error)
         goto cleanup;
         goto cleanup;
     }
     }
 
 
-    if (ERROR_SUCCESS != p_SetSecurityInfo
-        (GetCurrentProcess(), SE_KERNEL_OBJECT,
-         OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
-         usersid, NULL, acl, NULL)) {
+    if (ERROR_SUCCESS != p_SetSecurityInfo(
+            GetCurrentProcess(), SE_KERNEL_OBJECT,
+            OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
+            usersid, NULL, acl, NULL)) {
         *error = dupprintf("Unable to set process ACL: %s",
         *error = dupprintf("Unable to set process ACL: %s",
                            win_strerror(GetLastError()));
                            win_strerror(GetLastError()));
         goto cleanup;
         goto cleanup;

部分文件因为文件数量过多而无法显示