1
0
Эх сурвалжийг харах

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 жил өмнө
parent
commit
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. */
     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;
 }
 
+/*
+ * 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
  * 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);
 }
 
+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);
 
 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)                                                 \
     /*static WINSCP*/ void aes##len##_ni_cbc_encrypt(                              \
         ssh_cipher *ciph, void *vblk, int blklen)                       \
@@ -305,6 +359,12 @@ static inline void aes_sdctr_ni(
     /*static WINSCP*/ void aes##len##_ni_sdctr(                                    \
         ssh_cipher *ciph, void *vblk, int blklen)                       \
     { 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(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(...)
 #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 *                                        \
     ssh_aes ## bits ## _ ## mode_c ## _impls[] = {                      \
         IF_NI(&ssh_aes ## bits ## _ ## mode_c ## _ni,)                  \
@@ -54,7 +54,7 @@ static ssh_cipher *aes_select(const ssh_cipheralg *alg)
         /* WINSCP */ \
         /*.new =*/ aes_select,                                              \
         NULL, NULL, NULL, NULL, NULL, NULL, NULL, \
-        /*.ssh2_id =*/ "aes" #bits "-" mode_protocol,                       \
+        /*.ssh2_id =*/ id                                                   \
         /*.blksize =*/ 16,                                                  \
         /*.real_keybits =*/ bits,                                           \
         /*.padded_keybytes =*/ bits/8,                                      \
@@ -63,14 +63,26 @@ static ssh_cipher *aes_select(const ssh_cipheralg *alg)
         " (dummy selector vtable)",                                     \
         NULL, \
         /*.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 = {
     /* 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 };
+
+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_pos;
         } 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;
     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);
 }
 
+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
 
 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)                                 \
     /*WINSCP static*/ void aes##len##_sw_cbc_encrypt(              \
         ssh_cipher *ciph, void *vblk, int blklen)       \
@@ -1048,7 +1135,13 @@ static inline void aes_sdctr_sw(
     { aes_cbc_sw_decrypt(ciph, vblk, blklen); }         \
     /*WINSCP static*/ void aes##len##_sw_sdctr(                    \
         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
 

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

@@ -15,6 +15,11 @@ struct aes_extra {
 
     /* Point to a writable substructure. */
     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 {
     bool checked_availability;
@@ -30,6 +35,17 @@ static inline bool check_availability(const struct aes_extra *extra)
     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
  * 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.
  */
 
-#define AES_EXTRA(impl_c)                                               \
+#define AES_EXTRA_BITS(impl_c, bits)                                    \
     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 */ \
         /*.check_available =*/ aes ## impl_c ## _available,                 \
         /*.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 
 // Warning 1060: Different alignments specified for same segment, %s. Using highest alignment.rdata
 // in objconv
@@ -60,6 +82,7 @@ static inline bool check_availability(const struct aes_extra *extra)
         /*.setkey =*/ aes ## impl_c ## _setkey,                             \
         /*.encrypt =*/ aes ## bits ## impl_c ## _cbc_encrypt,               \
         /*.decrypt =*/ aes ## bits ## impl_c ## _cbc_decrypt,               \
+        .next_message = nullcipher_next_message,                        \
         NULL, \
         NULL, \
         /*.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,                                     \
         /*.text_name =*/ ssh_aes ## bits ## _cbc ## impl_c ## text_name, /*WINSCP*/ \
         NULL, \
-        /*.extra =*/ &aes ## impl_c ## _extra,                              \
+        /*.extra =*/ &aes ## bits ## impl_c ## _extra,                              \
     }
 
 #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,                     \
         NULL, \
         NULL, \
+        .next_message = nullcipher_next_message,                        \
         /*.ssh2_id =*/ ssh_aes ## bits ## _sdctr ## impl_c ## ssh2_id, /*WINSCP*/ \
         /*.blksize =*/ 16,                                                  \
         /*.real_keybits =*/ bits,                                           \
@@ -92,7 +116,31 @@ static inline bool check_availability(const struct aes_extra *extra)
         /*.flags =*/ 0,                                                     \
         /*.text_name =*/ ssh_aes ## bits ## _sdctr ## impl_c ## text_name, /*WINSCP*/ \
         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)           \
@@ -101,7 +149,10 @@ static inline bool check_availability(const struct aes_extra *extra)
     AES_CBC_VTABLE(impl_c, impl_display, 256);          \
     AES_SDCTR_VTABLE(impl_c, impl_display, 128);        \
     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

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

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

@@ -11,8 +11,8 @@
 #include "ssh.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;
     BlowfishContext *ctx;
@@ -32,9 +32,9 @@ BlowfishContext *bcrypt_setup(const unsigned char *key, int keybytes,
     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;
     int i;
@@ -49,10 +49,10 @@ void bcrypt_hash(const unsigned char *key, int keybytes,
     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];
 

+ 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 )
 
 static void blowfish_encrypt(uint32_t xL, uint32_t xR, uint32_t *output,
-                             BlowfishContext * ctx)
+                             BlowfishContext *ctx)
 {
     uint32_t *S0 = ctx->S0;
     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,
-                             BlowfishContext * ctx)
+                             BlowfishContext *ctx)
 {
     uint32_t *S0 = ctx->S0;
     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,
-                                     BlowfishContext * ctx)
+                                     BlowfishContext *ctx)
 {
     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;
 }
 
-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;
     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,
-                                     BlowfishContext * ctx)
+                                     BlowfishContext *ctx)
 {
     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,
-                                     BlowfishContext * ctx)
+                                     BlowfishContext *ctx)
 {
     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,
-                                     BlowfishContext * ctx)
+                                     BlowfishContext *ctx)
 {
     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,
-                                     BlowfishContext * ctx)
+                               BlowfishContext *ctx)
 {
     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 *vsalt, short saltbytes)
 {
@@ -655,7 +655,9 @@ const ssh_cipheralg ssh_blowfish_ssh1 = {
     /*.setkey =*/ blowfish_ssh_setkey,
     /*.encrypt =*/ blowfish_ssh1_encrypt_blk,
     /*.decrypt =*/ blowfish_ssh1_decrypt_blk,
-    NULL, NULL, NULL, // WINSCP
+    NULL, NULL, // WINSCP
+    .next_message = nullcipher_next_message,
+    NULL, // WINSCP
     /*.blksize =*/ 8,
     /*.real_keybits =*/ 128,
     /*.padded_keybytes =*/ SSH1_SESSION_KEY_LENGTH,
@@ -673,6 +675,7 @@ const ssh_cipheralg ssh_blowfish_ssh2 = {
     /*.encrypt =*/ blowfish_ssh2_encrypt_blk,
     /*.decrypt =*/ blowfish_ssh2_decrypt_blk,
     NULL, NULL, // WINSCP
+    .next_message = nullcipher_next_message,
     /*.ssh2_id =*/ "blowfish-cbc",
     /*.blksize =*/ 8,
     /*.real_keybits =*/ 128,
@@ -691,6 +694,7 @@ const ssh_cipheralg ssh_blowfish_ssh2_ctr = {
     /*.encrypt =*/ blowfish_ssh2_sdctr,
     /*.decrypt =*/ blowfish_ssh2_sdctr,
     NULL, NULL, // WINSCP
+    .next_message = nullcipher_next_message,
     /*.ssh2_id =*/ "blowfish-ctr",
     /*.blksize =*/ 8,
     /*.real_keybits =*/ 256,

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

@@ -869,6 +869,7 @@ struct ccp_context {
     BinarySink_IMPLEMENTATION;
     ssh_cipher ciph;
     ssh2_mac mac_if;
+    bool ciph_allocated, mac_allocated;
 };
 
 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);
     ctx->mac_if.vt = alg;
+    ctx->mac_allocated = true;
     BinarySink_DELEGATE_INIT(&ctx->mac_if, ctx);
     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)
 {
-    /* 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)
@@ -950,6 +965,7 @@ const ssh2_macalg ssh2_poly1305 = {
     /*.setkey =*/ poly_setkey,
     /*.start =*/ poly_start,
     /*.genresult =*/ poly_genresult,
+    .next_message = nullmac_next_message,
     /*.text_name =*/ poly_text_name,
     /*.name =*/ "",
     /*.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);
     poly1305_init(&ctx->mac);
     ctx->ciph.vt = alg;
+    ctx->ciph_allocated = true;
+    ctx->mac_allocated = false;
     return &ctx->ciph;
 }
 
 static void ccp_free(ssh_cipher *cipher)
 {
     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)
@@ -1049,6 +1065,7 @@ const ssh_cipheralg ssh2_chacha20_poly1305 = {
     /*.decrypt =*/ ccp_decrypt,
     /*.encrypt_length =*/ ccp_encrypt_length,
     /*.decrypt_length =*/ ccp_decrypt_length,
+    .next_message = nullcipher_next_message,
     /*.ssh2_id =*/ "[email protected]",
     /*.blksize =*/ 1,
     /*.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;
     }
     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 */
     s40 ^= (s40 << 4) & ((0xf000/0x004) * (c40 & 0x00040004));
@@ -699,6 +699,7 @@ const ssh_cipheralg ssh_des = {
     /*.encrypt =*/ des_cbc_encrypt,
     /*.decrypt =*/ des_cbc_decrypt,
     NULL, NULL, // WINSCP
+    .next_message = nullcipher_next_message,
     /*.ssh2_id =*/ "des-cbc",
     /*.blksize =*/ 8,
     /*.real_keybits =*/ 56,
@@ -718,6 +719,7 @@ const ssh_cipheralg ssh_des_sshcom_ssh2 = {
     /*.encrypt =*/ des_cbc_encrypt,
     /*.decrypt =*/ des_cbc_decrypt,
     NULL, NULL, // WINSCP
+    .next_message = nullcipher_next_message,
     /*.ssh2_id =*/ "[email protected]",
     /*.blksize =*/ 8,
     /*.real_keybits =*/ 56,
@@ -829,6 +831,7 @@ const ssh_cipheralg ssh_3des_ssh2 = {
     /*.encrypt =*/ des3_cbc1_cbc_encrypt,
     /*.decrypt =*/ des3_cbc1_cbc_decrypt,
     NULL, NULL, // WINSCP
+    .next_message = nullcipher_next_message,
     /*.ssh2_id =*/ "3des-cbc",
     /*.blksize =*/ 8,
     /*.real_keybits =*/ 168,
@@ -938,6 +941,7 @@ const ssh_cipheralg ssh_3des_ssh2_ctr = {
     /*.encrypt =*/ des3_sdctr_encrypt_decrypt,
     /*.decrypt =*/ des3_sdctr_encrypt_decrypt,
     NULL, NULL, // WINSCP
+    .next_message = nullcipher_next_message,
     /*.ssh2_id =*/ "3des-ctr",
     /*.blksize =*/ 8,
     /*.real_keybits =*/ 168,
@@ -1082,6 +1086,7 @@ const ssh_cipheralg ssh_3des_ssh1 = {
     /*.encrypt =*/ des3_cbc3_cbc_encrypt,
     /*.decrypt =*/ des3_cbc3_cbc_decrypt,
     NULL, NULL, // WINSCP
+    .next_message = nullcipher_next_message,
     NULL, // WINSCP
     /*.blksize =*/ 8,
     /*.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);
 }
 
+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)
 {
     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 *private_key,
-                     unsigned char *digest, int digest_len)
+                  mp_int *private_key,
+                  unsigned char *digest, int digest_len)
 {
     /*
      * The basic DSA signing algorithm is:
@@ -504,6 +510,8 @@ static void dsa_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs)
     } // WINSCP
 }
 
+static char *dsa_alg_desc(const ssh_keyalg *self) { return dupstr("DSA"); }
+
 const ssh_keyalg ssh_dsa = {
     // WINSCP
     /*.new_pub =*/ dsa_new_pub,
@@ -516,10 +524,16 @@ const ssh_keyalg ssh_dsa = {
     /*.public_blob =*/ dsa_public_blob,
     /*.private_blob =*/ dsa_private_blob,
     /*.openssh_blob =*/ dsa_openssh_blob,
+    .has_private = dsa_has_private,
     /*.cache_str =*/ dsa_cache_str,
     /*.components =*/ dsa_components,
+    .base_key = nullkey_base_key,
     /*.pubkey_bits =*/ dsa_pubkey_bits,
     /*.ssh_id =*/ "ssh-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:
  *
  * 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
  * lot of underlying mechanism to SEC 1:
@@ -402,6 +402,9 @@ struct ecsign_extra {
     const unsigned char *oid;
     int oidlen;
 
+    /* Human-readable algorithm description */
+    const char *alg_desc;
+
     /* Some EdDSA instances prefix a string to all hash preimages, to
      * disambiguate which signature variant they're being used with */
     ptrlen hash_prefix;
@@ -903,6 +906,12 @@ static void ecdsa_private_blob(ssh_key *key, BinarySink *bs)
     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)
 {
     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);
 }
 
+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)
 {
     ssh_key *sshk = ecdsa_new_pub(alg, pub);
@@ -1441,9 +1456,16 @@ static void eddsa_sign(ssh_key *key, ptrlen data,
     } // 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 = {
     ec_ed25519, &ssh_sha512,
-    NULL, 0, PTRLEN_DECL_LITERAL(""),
+    NULL, 0, "Ed25519", PTRLEN_DECL_LITERAL(""),
 };
 const ssh_keyalg ssh_ecdsa_ed25519 = {
     // WINSCP
@@ -1457,18 +1479,24 @@ const ssh_keyalg ssh_ecdsa_ed25519 = {
     /*.public_blob =*/ eddsa_public_blob,
     /*.private_blob =*/ eddsa_private_blob,
     /*.openssh_blob =*/ eddsa_openssh_blob,
+    .has_private = eddsa_has_private,
     /*.cache_str =*/ eddsa_cache_str,
     /*.components =*/ eddsa_components,
     /*.pubkey_bits =*/ ec_shared_pubkey_bits,
+    .base_key = nullkey_base_key,
     /*.ssh_id =*/ "ssh-ed25519",
     /*.cache_id =*/ "ssh-ed25519",
     /*.extra =*/ &sign_extra_ed25519,
     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 = {
     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 = {
     // WINSCP
@@ -1482,13 +1510,19 @@ const ssh_keyalg ssh_ecdsa_ed448 = {
     /*.public_blob =*/ eddsa_public_blob,
     /*.private_blob =*/ eddsa_private_blob,
     /*.openssh_blob =*/ eddsa_openssh_blob,
+    .has_private = eddsa_has_private,
     /*.cache_str =*/ eddsa_cache_str,
     /*.components =*/ eddsa_components,
     /*.pubkey_bits =*/ ec_shared_pubkey_bits,
+    .base_key = nullkey_base_key,
     /*.ssh_id =*/ "ssh-ed448",
     /*.cache_id =*/ "ssh-ed448",
     /*.extra =*/ &sign_extra_ed448,
     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) */
@@ -1497,7 +1531,7 @@ static const unsigned char nistp256_oid[] = {
 };
 static const struct ecsign_extra sign_extra_nistp256 = {
     ec_p256, &ssh_sha256,
-    nistp256_oid, lenof(nistp256_oid),
+    nistp256_oid, lenof(nistp256_oid), "NIST p256",
 };
 const ssh_keyalg ssh_ecdsa_nistp256 = {
     // WINSCP
@@ -1511,13 +1545,19 @@ const ssh_keyalg ssh_ecdsa_nistp256 = {
     /*.public_blob =*/ ecdsa_public_blob,
     /*.private_blob =*/ ecdsa_private_blob,
     /*.openssh_blob =*/ ecdsa_openssh_blob,
+    .has_private = ecdsa_has_private,
     /*.cache_str =*/ ecdsa_cache_str,
     /*.components =*/ ecdsa_components,
     /*.pubkey_bits =*/ ec_shared_pubkey_bits,
+    .base_key = nullkey_base_key,
     /*.ssh_id =*/ "ecdsa-sha2-nistp256",
     /*.cache_id =*/ "ecdsa-sha2-nistp256",
     /*.extra =*/ &sign_extra_nistp256,
     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) */
@@ -1526,7 +1566,7 @@ static const unsigned char nistp384_oid[] = {
 };
 static const struct ecsign_extra sign_extra_nistp384 = {
     ec_p384, &ssh_sha384,
-    nistp384_oid, lenof(nistp384_oid),
+    nistp384_oid, lenof(nistp384_oid), "NIST p384",
 };
 const ssh_keyalg ssh_ecdsa_nistp384 = {
     // WINSCP
@@ -1540,13 +1580,19 @@ const ssh_keyalg ssh_ecdsa_nistp384 = {
     /*.public_blob =*/ ecdsa_public_blob,
     /*.private_blob =*/ ecdsa_private_blob,
     /*.openssh_blob =*/ ecdsa_openssh_blob,
+    .has_private = ecdsa_has_private,
     /*.cache_str =*/ ecdsa_cache_str,
     /*.components =*/ ecdsa_components,
     /*.pubkey_bits =*/ ec_shared_pubkey_bits,
+    .base_key = nullkey_base_key,
     /*.ssh_id =*/ "ecdsa-sha2-nistp384",
     /*.cache_id =*/ "ecdsa-sha2-nistp384",
     /*.extra =*/ &sign_extra_nistp384,
     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) */
@@ -1555,7 +1601,7 @@ static const unsigned char nistp521_oid[] = {
 };
 static const struct ecsign_extra sign_extra_nistp521 = {
     ec_p521, &ssh_sha512,
-    nistp521_oid, lenof(nistp521_oid),
+    nistp521_oid, lenof(nistp521_oid), "NIST p521",
 };
 const ssh_keyalg ssh_ecdsa_nistp521 = {
     // WINSCP
@@ -1569,137 +1615,153 @@ const ssh_keyalg ssh_ecdsa_nistp521 = {
     /*.public_blob =*/ ecdsa_public_blob,
     /*.private_blob =*/ ecdsa_private_blob,
     /*.openssh_blob =*/ ecdsa_openssh_blob,
+    .has_private = ecdsa_has_private,
     /*.cache_str =*/ ecdsa_cache_str,
     /*.components =*/ ecdsa_components,
     /*.pubkey_bits =*/ ec_shared_pubkey_bits,
+    .base_key = nullkey_base_key,
     /*.ssh_id =*/ "ecdsa-sha2-nistp521",
     /*.cache_id =*/ "ecdsa-sha2-nistp521",
     /*.extra =*/ &sign_extra_nistp521,
     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 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 ec_curve *curve;
     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;
-    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);
-    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);
 
-    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();
-    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
      * 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 */
     { // WINSCP
     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
 
     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)
 {
-    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)
 {
+    ecdh_key_m *dhm = container_of(dh, ecdh_key_m, ek);
     mp_int *x;
     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));
     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)
-        return NULL;
+        return false;
 
     if (ecc_weierstrass_is_identity(remote_p)) {
         /* Not a sensible Diffie-Hellman input value */
         ecc_weierstrass_point_free(remote_p);
-        return NULL;
+        return false;
     }
 
     { // WINSCP
-    WeierstrassPoint *p = ecc_weierstrass_multiply(remote_p, dh->private);
+    WeierstrassPoint *p = ecc_weierstrass_multiply(remote_p, dhw->private);
 
     mp_int *x;
     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(p);
 
-    return x;
+    return true;
     } // 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);
 
     /* 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
      * still fits into the remaining number of bits is accepted, and
      * will be reduced mod p. */
-    mp_reduce_mod_2to(remote_x, dh->curve->fieldBits);
+    mp_reduce_mod_2to(remote_x, dhm->curve->fieldBits);
 
     { // WINSCP
     MontgomeryPoint *remote_p = ecc_montgomery_point_new(
-        dh->curve->m.mc, remote_x);
+        dhm->curve->m.mc, remote_x);
     mp_free(remote_x);
 
     { // 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)) {
         ecc_montgomery_point_free(remote_p);
         ecc_montgomery_point_free(p);
-        return NULL;
+        return false;
     }
 
     { // WINSCP
@@ -1747,104 +1809,142 @@ static mp_int *ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey)
     { // WINSCP
     strbuf *sb = strbuf_new();
     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));
     mp_free(x);
     x = mp_from_bytes_be(ptrlen_from_strbuf(sb));
     strbuf_free(sb);
+    put_mp_ssh2(bs, x);
+    mp_free(x);
 
-    return x;
+    return true;
     } // 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 = {
-    "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 */
-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 = {
-    "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 = {
-    "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 = {
-    "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 = {
-    "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[] = {
@@ -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 };
 
+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
  * data.
  */
 
 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[] = {
         &ssh_ecdsa_nistp256,

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

@@ -175,6 +175,7 @@ const ssh2_macalg ssh_hmac_sha256 = {
     /*.setkey =*/ hmac_key,
     /*.start =*/ hmac_start,
     /*.genresult =*/ hmac_genresult,
+    .next_message = nullmac_next_message,
     /*.text_name =*/ hmac_text_name,
     /*.name =*/ "hmac-sha2-256",
     /*.etm_name =*/ "[email protected]",
@@ -191,6 +192,7 @@ const ssh2_macalg ssh_hmac_md5 = {
     /*.setkey =*/ hmac_key,
     /*.start =*/ hmac_start,
     /*.genresult =*/ hmac_genresult,
+    .next_message = nullmac_next_message,
     /*.text_name =*/ hmac_text_name,
     /*.name =*/ "hmac-md5",
     /*.etm_name =*/ "[email protected]",
@@ -208,6 +210,7 @@ const ssh2_macalg ssh_hmac_sha1 = {
     /*.setkey =*/ hmac_key,
     /*.start =*/ hmac_start,
     /*.genresult =*/ hmac_genresult,
+    .next_message = nullmac_next_message,
     /*.text_name =*/ hmac_text_name,
     /*.name =*/ "hmac-sha1",
     /*.etm_name =*/ "[email protected]",
@@ -225,6 +228,7 @@ const ssh2_macalg ssh_hmac_sha1_96 = {
     /*.setkey =*/ hmac_key,
     /*.start =*/ hmac_start,
     /*.genresult =*/ hmac_genresult,
+    .next_message = nullmac_next_message,
     /*.text_name =*/ hmac_text_name,
     /*.name =*/ "hmac-sha1-96",
     /*.etm_name =*/ "[email protected]",
@@ -244,6 +248,7 @@ const ssh2_macalg ssh_hmac_sha1_buggy = {
     /*.setkey =*/ hmac_key,
     /*.start =*/ hmac_start,
     /*.genresult =*/ hmac_genresult,
+    .next_message = nullmac_next_message,
     /*.text_name =*/ hmac_text_name,
     /*.name =*/ "hmac-sha1",
     NULL, // WINSCP
@@ -263,6 +268,7 @@ const ssh2_macalg ssh_hmac_sha1_96_buggy = {
     /*.setkey =*/ hmac_key,
     /*.start =*/ hmac_start,
     /*.genresult =*/ hmac_genresult,
+    .next_message = nullmac_next_message,
     /*.text_name =*/ hmac_text_name,
     /*.name =*/ "hmac-sha1-96",
     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);
 }
 
+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 *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;
 }
 
+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)
 {
     mp_int *b1, *b2;
@@ -541,6 +556,12 @@ static key_components *rsa2_components(ssh_key *key)
     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)
 {
     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,
-                               ptrlen pub, ptrlen priv)
+                              ptrlen pub, ptrlen priv)
 {
     BinarySource src[1];
     ssh_key *sshk;
@@ -867,6 +888,23 @@ static char *rsa2_invalid(ssh_key *key, unsigned flags)
     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
     rsa_extra = { 0 },
     rsa_sha256_extra = { SSH_AGENT_RSA_SHA2_256 },
@@ -885,8 +923,12 @@ static const struct ssh2_rsa_extra
     /*.private_blob =*/ rsa2_private_blob,          \
     /*.openssh_blob =*/ rsa2_openssh_blob,          \
     /*.cache_str =*/ rsa2_cache_str,                \
+    .has_private = rsa2_has_private,            \
     /*.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 \
     /*.cache_id =*/ "rsa2"
 
@@ -895,8 +937,9 @@ const ssh_keyalg ssh_rsa = {
     COMMON_KEYALG_FIELDS,
     /*.ssh_id =*/ "ssh-rsa",
     COMMON_KEYALG_FIELDS2,
+    /*.supported_flags =*/ ssh_rsa_supported_flags,
+    .alternate_ssh_id = ssh_rsa_alternate_ssh_id,
     /*.extra =*/ &rsa_extra,
-    /*.supported_flags =*/ SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512,
 };
 
 const ssh_keyalg ssh_rsa_sha256 = {
@@ -905,7 +948,8 @@ const ssh_keyalg ssh_rsa_sha256 = {
     /*.ssh_id =*/ "rsa-sha2-256",
     COMMON_KEYALG_FIELDS2,
     /*.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 = {
@@ -914,7 +958,8 @@ const ssh_keyalg ssh_rsa_sha512 = {
     /*.ssh_id =*/ "rsa-sha2-512",
     COMMON_KEYALG_FIELDS2,
     /*.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)
@@ -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 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 = {
-    "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[] = {

+ 22 - 0
source/putty/defs.h

@@ -127,6 +127,9 @@ typedef struct LogPolicyVtable LogPolicyVtable;
 
 typedef struct Seat Seat;
 typedef struct SeatVtable SeatVtable;
+typedef struct SeatDialogText SeatDialogText;
+typedef struct SeatDialogTextItem SeatDialogTextItem;
+typedef struct SeatDialogPromptDescriptions SeatDialogPromptDescriptions;
 typedef struct SeatPromptResult SeatPromptResult;
 
 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 mainchan mainchan;
 
+typedef struct CertExprBuilder CertExprBuilder;
+
 typedef struct ssh_sharing_state ssh_sharing_state;
 typedef struct ssh_sharing_connstate ssh_sharing_connstate;
 typedef struct share_channel share_channel;
@@ -180,12 +185,19 @@ typedef struct ssh_cipher ssh_cipher;
 typedef struct ssh2_ciphers ssh2_ciphers;
 typedef struct dh_ctx dh_ctx;
 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 dlgcontrol dlgcontrol;
 
 typedef struct settings_w settings_w;
 typedef struct settings_r settings_r;
 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;
 
@@ -254,4 +266,14 @@ struct unicode_data;
 #define CAT_INNER(x,y) 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 */

+ 49 - 51
source/putty/import.c

@@ -180,17 +180,6 @@ bool export_ssh2(const Filename *filename, int type,
     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.)
  */
@@ -334,7 +323,7 @@ struct openssh_pem_key {
     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;
     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;
     return ret;
 
-    error:
+  error:
     if (line) {
         smemclr(line, strlen(line));
         sfree(line);
@@ -809,7 +798,7 @@ static ssh2_userkey *openssh_pem_read(
     errmsg = NULL;                     /* no error */
     retval = retkey;
 
-    error:
+  error:
     strbuf_free(blob);
     strbuf_free(key->keyblob);
     smemclr(key, sizeof(*key));
@@ -819,7 +808,7 @@ static ssh2_userkey *openssh_pem_read(
 }
 
 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;
     unsigned char *spareblob;
@@ -833,13 +822,17 @@ static bool openssh_pem_write(
     FILE *fp;
     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.
      */
     pubblob = strbuf_new();
-    ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob));
+    ssh_key_public_blob(key, BinarySink_UPCAST(pubblob));
     privblob = strbuf_new_nm();
-    ssh_key_private_blob(key->key, BinarySink_UPCAST(privblob));
+    ssh_key_private_blob(key, BinarySink_UPCAST(privblob));
     spareblob = NULL;
 
     outblob = strbuf_new_nm();
@@ -848,8 +841,8 @@ static bool openssh_pem_write(
      * Encode the OpenSSH key blob, and also decide on the header
      * 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;
 
         /*
@@ -859,7 +852,7 @@ static bool openssh_pem_write(
          * bignums per key type and then construct the actual blob in
          * 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;
             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_data(outblob, seq->s, seq->len);
         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;
-        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 pointlen;
         strbuf *seq, *sub;
@@ -974,7 +967,7 @@ static bool openssh_pem_write(
          *   [1]
          *     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;
 
         seq = strbuf_new_nm();
@@ -1006,7 +999,7 @@ static bool openssh_pem_write(
 
         /* Append the BIT STRING to the sequence */
         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);
         strbuf_free(sub);
 
@@ -1083,12 +1076,12 @@ static bool openssh_pem_write(
             fprintf(fp, "%02X", iv[i]);
         fprintf(fp, "\n\n");
     }
-    base64_encode(fp, outblob->u, outblob->len, 64);
+    base64_encode_fp(fp, ptrlen_from_strbuf(outblob), 64);
     fputs(footer, fp);
     fclose(fp);
     ret = true;
 
-    error:
+  error:
     if (outblob)
         strbuf_free(outblob);
     if (spareblob) {
@@ -1253,8 +1246,8 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
         ret->kdfopts.bcrypt.rounds = get_uint32(opts);
 
         if (get_err(opts)) {
-          errmsg = "failed to parse bcrypt options string";
-          goto error;
+            errmsg = "failed to parse bcrypt options string";
+            goto error;
         }
         break;
       }
@@ -1302,7 +1295,7 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
     if (errmsg_p) *errmsg_p = NULL;
     return ret;
 
-    error:
+  error:
     if (line) {
         smemclr(line, strlen(line));
         sfree(line);
@@ -1492,7 +1485,7 @@ static ssh2_userkey *openssh_new_read(
     retval = retkey;
     retkey = NULL;                     /* prevent the free */
 
-    error:
+  error:
     if (retkey) {
         sfree(retkey->comment);
         if (retkey->key)
@@ -1507,7 +1500,7 @@ static ssh2_userkey *openssh_new_read(
 }
 
 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;
     int padvalue;
@@ -1517,13 +1510,17 @@ static bool openssh_new_write(
     const int bcrypt_rounds = 16;
     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.
      */
     pubblob = strbuf_new();
-    ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob));
+    ssh_key_public_blob(key, BinarySink_UPCAST(pubblob));
     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.
@@ -1570,11 +1567,11 @@ static bool openssh_new_write(
 
         /* Private key. The main private blob goes inline, with no string
          * 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);
 
         /* Comment. */
-        put_stringz(cpblob, key->comment);
+        put_stringz(cpblob, ukey->comment);
 
         /* Pad out the encrypted section. */
         padvalue = 1;
@@ -1614,12 +1611,12 @@ static bool openssh_new_write(
     if (!fp)
         goto error;
     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);
     fclose(fp);
     ret = true;
 
-    error:
+  error:
     if (cblob)
         strbuf_free(cblob);
     if (privblob)
@@ -1641,11 +1638,12 @@ static bool openssh_auto_write(
      * assume that anything not in that fixed list is newer, and hence
      * 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);
     else
         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;
     return ret;
 
-    error:
+  error:
     if (line) {
         smemclr(line, strlen(line));
         sfree(line);
@@ -1891,7 +1889,7 @@ static bool sshcom_encrypted(BinarySource *filesrc, char **comment)
     if (!ptrlen_eq_string(str, "none"))
         answer = true;
 
-    done:
+  done:
     if (key) {
         *comment = dupstr(key->comment);
         strbuf_free(key->keyblob);
@@ -1903,7 +1901,7 @@ static bool sshcom_encrypted(BinarySource *filesrc, char **comment)
     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;
     size_t nbytes = str.len;
@@ -1987,7 +1985,7 @@ static ssh2_userkey *sshcom_read(
         !memcmp(str.ptr, prefix_rsa, sizeof(prefix_rsa) - 1)) {
         type = RSA;
     } 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;
     } else {
         errmsg = "key is of unknown type";
@@ -2142,7 +2140,7 @@ static ssh2_userkey *sshcom_read(
     errmsg = NULL; /* no error */
     ret = retkey;
 
-    error:
+  error:
     if (blob) {
         strbuf_free(blob);
     }
@@ -2316,12 +2314,12 @@ static bool sshcom_write(
         }
         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);
     fclose(fp);
     ret = true;
 
-    error:
+  error:
     if (outblob)
         strbuf_free(outblob);
     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_avail(src) (BinarySource_UPCAST(src)->len - \
-                       BinarySource_UPCAST(src)->pos)
+                        BinarySource_UPCAST(src)->pos)
 #define get_ptr(src)                                                    \
     ((const void *)(                                                    \
         (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_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_append(strbuf *buf, size_t 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(int codepage, int flags, const char *string);
 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,
-                   const char *defchr, struct unicode_data *ucsdata);
+                   const char *defchr);
 
 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);
 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_tag {
@@ -158,6 +176,21 @@ static inline ptrlen make_ptrlen(const void *ptr, size_t len)
     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)
 {
     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_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail);
 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);
 int string_length_for_printf(size_t);
 /* 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. */
 #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
  * than memset because we don't need the fill char parameter; also
  * 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
  * data-dependent control flow (so an attacker with a very accurate
  * 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
  * (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. */
 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

+ 7 - 0
source/putty/mpint.h

@@ -42,6 +42,13 @@ mp_int *mp_new(size_t maxbits);
 void mp_free(mp_int *);
 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
  * 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 {
     char buf[8192];
     size_t size;
+    const char *prefix;                /* must be statically allocated */
 } ProxyStderrBuf;
 void psb_init(ProxyStderrBuf *psb);
+void psb_set_prefix(ProxyStderrBuf *psb, const char *prefix);
 void log_proxy_stderr(
     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)
 { dso->vt->free(dso); }
 
+DeferredSocketOpener *null_deferred_socket_opener(void);
+
 #endif

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

@@ -26,7 +26,8 @@ strbuf *chap_response(ptrlen challenge, ptrlen password)
     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 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
  * socket can begin working normally.
  */
-void proxy_activate(ProxySocket *ps)
+static void proxy_activate(ProxySocket *ps)
 {
     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);
 }
 
-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);
     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,
-                     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 &&
         do_proxy_dns(conf) &&
@@ -516,7 +516,9 @@ Socket *new_connection(SockAddr *addr, const char *hostname,
         char *proxy_canonical_name;
         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,
                                             oobinline, nodelay, keepalive,
                                             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"
-                                      " 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);
             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;
 
                 for (;;) {
-                  eo++;
-                  if (fmt[eo] >= '0' && fmt[eo] <= '9')
-                      v += fmt[eo] - '0';
-                  else if (fmt[eo] >= 'a' && fmt[eo] <= 'f')
-                      v += fmt[eo] - 'a' + 10;
-                  else if (fmt[eo] >= 'A' && fmt[eo] <= 'F')
-                      v += fmt[eo] - 'A' + 10;
-                  else {
-                    /* non hex character, so we abort and just
-                     * send the whole thing unescaped (including \x)
-                     */
-                    put_byte(buf, '\\');
-                    eo = so + 1;
-                    break;
-                  }
-
-                  /* we only extract two hex characters */
-                  if (i == 1) {
-                    put_byte(buf, v);
                     eo++;
-                    break;
-                  }
-
-                  i++;
-                  v <<= 4;
+                    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;
               }

+ 137 - 23
source/putty/putty.h

@@ -266,7 +266,6 @@ struct sesslist {
 };
 
 struct unicode_data {
-    char **uni_tbl;
     bool dbcs_screenfont;
     int font_codepage;
     int line_codepage;
@@ -423,9 +422,14 @@ enum {
     KEX_WARN,
     KEX_DHGROUP1,
     KEX_DHGROUP14,
+    KEX_DHGROUP15,
+    KEX_DHGROUP16,
+    KEX_DHGROUP17,
+    KEX_DHGROUP18,
     KEX_DHGEX,
     KEX_RSA,
     KEX_ECDH,
+    KEX_NTRU_HYBRID,
     KEX_MAX
 };
 
@@ -453,6 +457,7 @@ enum {
     CIPHER_DES,
     CIPHER_ARCFOUR,
     CIPHER_CHACHA20,
+    CIPHER_AESGCM,
     CIPHER_MAX                         /* no. ciphers (inc warn) */
 };
 
@@ -474,7 +479,8 @@ enum {
      * Proxy types.
      */
     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
 };
 
@@ -1084,6 +1090,24 @@ typedef enum SeatOutputType {
     SEAT_OUTPUT_STDOUT, SEAT_OUTPUT_STDERR
 } 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
  * everything that a back end might need to talk to its client for:
@@ -1258,9 +1282,8 @@ struct SeatVtable {
      */
     SeatPromptResult (*confirm_ssh_host_key)(
         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
@@ -1287,6 +1310,13 @@ struct SeatVtable {
         Seat *seat, const char *algname, const char *betteralgs,
         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
      * 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); }
 static inline SeatPromptResult seat_confirm_ssh_host_key(
     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)
 { 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(
     InteractionReadySeat iseat, const char *atyp, const char *aname,
     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)
 { return iseat.seat->vt->confirm_weak_cached_hostkey(
         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)
 { return seat->vt->is_utf8(seat); }
 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)
 { 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
  * spoofed by server-supplied auth-time output such as SSH banners */
 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);
 SeatPromptResult nullseat_confirm_ssh_host_key(
     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);
 SeatPromptResult nullseat_confirm_weak_crypto_primitive(
     Seat *seat, const char *algtype, const char *algname,
@@ -1502,6 +1541,7 @@ SeatPromptResult nullseat_confirm_weak_crypto_primitive(
 SeatPromptResult nullseat_confirm_weak_cached_hostkey(
     Seat *seat, const char *algname, const char *betteralgs,
     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_always_utf8(Seat *seat);
 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_window_pixel_size(Seat *seat, int *width, int *height);
 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);
 bool nullseat_can_set_trust_status_yes(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);
 SeatPromptResult console_confirm_ssh_host_key(
     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);
 SeatPromptResult console_confirm_weak_crypto_primitive(
     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,
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
 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);
 bool console_can_set_trust_status(Seat *seat);
 bool console_has_mixed_input_stream(Seat *seat);
+const SeatDialogPromptDescriptions *console_prompt_descriptions(Seat *seat);
 
 /*
  * Other centralised seat functions.
@@ -1634,6 +1675,17 @@ struct TermWinVtable {
 
     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 (*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(INT, INT, ssh_cipherlist) \
     X(FILENAME, NONE, keyfile) \
+    X(FILENAME, NONE, detached_cert) \
+    X(STR, NONE, auth_plugin) \
     /* \
      * Which SSH protocol to use. \
      * 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_chanreq) \
     X(INT, NONE, sshbug_dropstart) \
+    X(INT, NONE, sshbug_filter_kexinit) \
     /*                                                                \
      * ssh_simple means that we promise never to open any channel     \
      * 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 *);
 void term_free(Terminal *);
 void term_size(Terminal *, int, int, int);
+void term_resize_request_completed(Terminal *);
 void term_paint(Terminal *, int, int, int, int, bool);
 void term_scroll(Terminal *, int, 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);
 void term_set_trust_status(Terminal *term, bool trusted);
 void term_keyinput(Terminal *, int codepage, const void *buf, int len);
-void term_keyinputw(Terminal *, const wchar_t * widebuf, int len);
+void term_keyinputw(Terminal *, const wchar_t *widebuf, int len);
 void term_get_cursor_position(Terminal *term, int *x, int *y);
 void term_setup_window_titles(Terminal *term, const char *title_hostname);
 void term_notify_minimised(Terminal *term, bool minimised);
@@ -2183,7 +2239,9 @@ int format_arrow_key(char *buf, Terminal *term, int xkey,
                      bool shift, bool ctrl, bool alt, bool *consumed_alt);
 int format_function_key(char *buf, Terminal *term, int key_number,
                         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,
                               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,
              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);
 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_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
@@ -2579,22 +2636,69 @@ void cmdline_error(const char *, ...) PRINTF_LIKE(1, 2);
  * Exports from config.c.
  */
 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);
 #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 conf_editbox_handler(union control *ctrl, dlgparam *dlg,
+void conf_editbox_handler(dlgcontrol *ctrl, dlgparam *dlg,
                           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 conf_fontsel_handler(union control *ctrl, dlgparam *dlg,
+void conf_fontsel_handler(dlgcontrol *ctrl, dlgparam *dlg,
                           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,
                       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.
  */
@@ -2830,6 +2934,16 @@ void request_callback_notifications(toplevel_callback_notify_fn_t notify,
                                     void *ctx);
 #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
  * 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[] = {
     { "aes",        CIPHER_AES,             -1, -1 },
     { "chacha20",   CIPHER_CHACHA20,        CIPHER_AES, +1 },
+    { "aesgcm",     CIPHER_AESGCM,          CIPHER_CHACHA20, +1 },
     { "3des",       CIPHER_3DES,            -1, -1 },
     { "WARN",       CIPHER_WARN,            -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
  * in sync with those. */
 static const struct keyvalwhere kexnames[] = {
+    { "ntru-curve25519",    KEX_NTRU_HYBRID, -1, +1 },
     { "ecdh",               KEX_ECDH,       -1, +1 },
     /* This name is misleading: it covers both SHA-256 and SHA-1 variants */
     { "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 },
+    /* This one really is only SHA-1, though: */
     { "dh-group1-sha1",     KEX_DHGROUP1,   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 }
 };
 
@@ -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_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, "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_b(sesskey, "RFCEnviron", conf_get_bool(conf, CONF_rfc_environ));
     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, "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, "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, "LoginShell", conf_get_bool(conf, CONF_login_shell));
     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 version string or any other reports. */
         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",
-                   *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";
         char *raw;
         i = 2 - gppi_raw(sesskey, "BugDHGEx2", 0);
@@ -1043,6 +1059,8 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
 #endif
     gppb(sesskey, "SshNoShell", false, conf, CONF_ssh_no_shell);
     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);
     gppb(sesskey, "RFCEnviron", false, conf, CONF_rfc_environ);
     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, "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, "BugFilterKexinit", 1); conf_set_int(conf, CONF_sshbug_filter_kexinit, 2-i);
     conf_set_bool(conf, CONF_ssh_simple, false);
     gppb(sesskey, "StampUtmp", true, conf, CONF_stamp_utmp);
     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 struct ec_curve **curve);
+                                const struct ec_curve **curve);
 const unsigned char *ec_alg_oid(const ssh_keyalg *alg, int *oidlen);
 extern const int ec_nist_curve_lengths[], n_ec_nist_curve_lengths;
 extern const int ec_ed_curve_lengths[], n_ec_ed_curve_lengths;
@@ -548,22 +548,34 @@ struct eddsa_key {
 WeierstrassPoint *ecdsa_public(mp_int *private_key, const ssh_keyalg *alg);
 EdwardsPoint *eddsa_public(mp_int *private_key, const ssh_keyalg *alg);
 
+typedef 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 {
     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_new(void);
 void key_components_add_text(key_components *kc,
                              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,
                            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);
 
 /*
@@ -591,6 +603,7 @@ bool rsa_verify(RSAKey *key);
 void rsa_ssh1_public_blob(BinarySink *bs, RSAKey *key, RsaSsh1Order order);
 int rsa_ssh1_public_blob_len(ptrlen data);
 void rsa_ssh1_private_blob_agent(BinarySink *bs, RSAKey *key);
+void duprsakey(RSAKey *dst, const RSAKey *src);
 void freersapriv(RSAKey *key);
 void freersakey(RSAKey *key);
 key_components *rsa_components(RSAKey *key);
@@ -623,21 +636,12 @@ strbuf *ssh_rsakex_encrypt(
 mp_int *ssh_rsakex_decrypt(
     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
  */
 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
 
 struct ssh_cipher {
@@ -669,6 +673,9 @@ struct ssh_cipheralg {
                            unsigned long seq);
     void (*decrypt_length)(ssh_cipher *, void *blk, int len,
                            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;
     int blksize;
     /* 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(
     ssh_cipher *c, void *blk, int len, unsigned long 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)
 { return c->vt; }
 
+void nullcipher_next_message(ssh_cipher *);
+
 struct ssh2_ciphers {
     int nciphers;
     const ssh_cipheralg *const *list;
@@ -735,6 +746,7 @@ struct ssh2_macalg {
     void (*setkey)(ssh2_mac *, ptrlen key);
     void (*start)(ssh2_mac *);
     void (*genresult)(ssh2_mac *, unsigned char *);
+    void (*next_message)(ssh2_mac *);
     const char *(*text_name)(ssh2_mac *);
     const char *name, *etm_name;
     int len, keylen;
@@ -754,6 +766,8 @@ static inline void ssh2_mac_start(ssh2_mac *m)
 { m->vt->start(m); }
 static inline void ssh2_mac_genresult(ssh2_mac *m, unsigned char *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)
 { return m->vt->text_name(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);
 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
  * string with a given key in the most obvious way. */
 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 {
     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;
+    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 */
 };
 
+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 {
     int nkexes;
     const ssh_kex *const *list;
@@ -864,17 +889,34 @@ struct ssh_keyalg {
     void (*public_blob)(ssh_key *key, BinarySink *);
     void (*private_blob)(ssh_key *key, BinarySink *);
     void (*openssh_blob) (ssh_key *key, BinarySink *);
+    bool (*has_private) (ssh_key *key);
     char *(*cache_str) (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 */
     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 */
     const char *ssh_id;    /* string identifier in the SSH protocol */
     const char *cache_id;  /* identifier used in PuTTY's host key cache */
     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
 
@@ -901,10 +943,24 @@ static inline void ssh_key_private_blob(ssh_key *key, BinarySink *bs)
 { key->vt->private_blob(key, bs); }
 static inline void ssh_key_openssh_blob(ssh_key *key, BinarySink *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)
 { return key->vt->cache_str(key); }
 static inline key_components *ssh_key_components(ssh_key *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)
 { return self->pubkey_bits(self, blob); }
 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; }
 static inline const char *ssh_key_cache_id(ssh_key *key)
 { 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
@@ -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_neon;
 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_ni;
 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_neon;
 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_ni;
 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_neon;
 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_ni;
 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_arcfour;
 extern const ssh2_ciphers ssh2_ccp;
+extern const ssh2_ciphers ssh2_aesgcm;
 extern const ssh_hashalg ssh_md5;
 extern const ssh_hashalg ssh_sha1;
 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_kexes ssh_diffiehellman_group1;
 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_kex ssh_diffiehellman_group1_sha1;
 extern const ssh_kex ssh_diffiehellman_group14_sha256;
 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_sha2_kex;
+extern const ssh_kexes ssh_gssk5_ecdh_kex;
 extern const ssh_kexes ssh_rsa_kex;
 extern const ssh_kex ssh_ec_kex_curve25519;
 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_nistp521;
 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_rsa;
 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_nistp384;
 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_sha1;
 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_sha256;
 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;
 
 /* Special constructor: BLAKE2b can be instantiated with any hash
  * length up to 128 bytes */
 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
  * 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.
  */
 bool platform_aes_neon_available(void);
+bool platform_pmull_neon_available(void);
 bool platform_sha256_neon_available(void);
 bool platform_sha1_neon_available(void);
 bool platform_sha512_neon_available(void);
@@ -1270,11 +1434,7 @@ static inline bool is_base64_char(char c)
             c == '+' || c == '/' || c == '=');
 }
 
-extern int base64_decode_atom(const char *atom, unsigned char *out);
 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 */
 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_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 */
 LoadedFile *lf_load_keyfile(const Filename *filename, const char **errptr);
 LoadedFile *lf_load_keyfile_fp(FILE *fp, const char **errptr);
@@ -1390,12 +1553,36 @@ 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_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;
 
+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_N_FPTYPES (SSH_FPTYPE_SHA256 + 1)
 
 FingerprintType ssh2_pick_fingerprint(char **fingerprints,
                                       FingerprintType preferred_type);
@@ -1409,6 +1596,8 @@ void ssh2_write_pubkey(FILE *fp, const char *comment,
                        int keytype);
 char *ssh2_fingerprint_blob(ptrlen, 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(ssh_key *key);
 void ssh2_free_all_fingerprints(char **);
@@ -1423,7 +1612,7 @@ bool import_possible(int type);
 int import_target_type(int type);
 bool import_encrypted(const Filename *filename, int type, char **comment);
 bool import_encrypted_s(const Filename *filename, BinarySource *src,
-                      int type, char **comment);
+                        int type, char **comment);
 int import_ssh1(const Filename *filename, int type,
                 RSAKey *key, char *passphrase, const char **errmsg_p);
 int import_ssh1_s(BinarySource *src, int type,
@@ -1431,7 +1620,7 @@ int import_ssh1_s(BinarySource *src, int type,
 ssh2_userkey *import_ssh2(const Filename *filename, int type,
                           char *passphrase, const char **errmsg_p);
 ssh2_userkey *import_ssh2_s(BinarySource *src, int type,
-                          char *passphrase, const char **errmsg_p);
+                            char *passphrase, const char **errmsg_p);
 bool export_ssh1(const Filename *filename, int type,
                  RSAKey *key, char *passphrase);
 bool export_ssh2(const Filename *filename, int type,
@@ -1728,6 +1917,7 @@ void old_keyfile_warning(void);
     X(BUG_CHOKES_ON_WINADJ)                     \
     X(BUG_SENDS_LATE_REQUEST_REPLY)             \
     X(BUG_SSH2_OLDGEX)                          \
+    X(BUG_REQUIRES_FILTERED_KEXINIT)            \
     /* end of list */
 #define TMP_DECLARE_LOG2_ENUM(thing) log2_##thing,
 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)))
 
 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);
 
 SeatPromptResult verify_ssh_host_key(
     InteractionReadySeat iseat, Conf *conf, const char *host, int port,
     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;
 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);
 
 #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)
 {
-    /*
-     * 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)
         ssh2_mac_free(s->out.mac);
     if (s->out.cipher)
@@ -142,6 +133,12 @@ void ssh2_bpp_new_outgoing_crypto(
     s->out.etm_mode = etm_mode;
     if (mac) {
         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));
 
         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;
     if (mac) {
         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));
 
         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);
 
         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;
         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 */
+    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);
 }

+ 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)
 { return ch->vt->rcvd_exit_status(ch, status); }
 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); }
 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); }
 static inline bool chan_run_shell(Channel *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.
  */
 
-void add_to_commasep(strbuf *buf, const char *data)
+void add_to_commasep_pl(strbuf *buf, ptrlen data)
 {
     if (buf->len > 0)
         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)
@@ -867,7 +872,7 @@ bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin)
 
 #undef BITMAP_UNIVERSAL
 #undef BITMAP_CONDITIONAL
-#undef SSH1_BITMAP_WORD
+#undef SSH2_BITMAP_WORD
 
 /* ----------------------------------------------------------------------
  * 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(
     InteractionReadySeat iseat, Conf *conf, const char *host, int port,
     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
@@ -956,12 +961,155 @@ SeatPromptResult verify_ssh_host_key(
      * The key is either missing from the cache, or does not match.
      * 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
 }
 

+ 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)) {
         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->name = dupstr("Authentication successful");
         add_prompt(
@@ -1605,8 +1605,8 @@ static void ssh2_delete_sharing_channel(ConnectionLayer *cl, unsigned localid)
 }
 
 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 =
         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 =
             gss->inquire_cred_by_mech(&gssctx->min_stat, cred,
                                       (gss_OID) GSS_MECH_KRB5,
-                                      GSS_C_NO_NAME,
+                                      NULL,
                                       &time_rec,
                                       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;
             }
         }
-        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
          * involve user interaction. */
@@ -183,23 +185,19 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
             mp_free(s->p); s->p = NULL;
         }
     } 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);
+        sfree(desc);
+
         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);
         {
             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);
         }
 
@@ -222,7 +220,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
 
         {
             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);
             strbuf_free(pubpoint);
         }
@@ -230,8 +228,9 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
         {
             ptrlen keydata = get_string(pktin);
             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 "
                                 "point in ECDH reply");
                 *aborted = true;
@@ -246,10 +245,10 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
             return;
         }
 
-        ssh_ecdhkex_freekey(s->ecdh_key);
+        ecdh_key_free(s->ecdh_key);
         s->ecdh_key = NULL;
 #ifndef NO_GSSAPI
-    } else if (s->kex_alg->main_type == KEXTYPE_GSS) {
+    } else if (kex_is_gss(s->kex_alg)) {
         ptrlen data;
 
         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)
             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
              * much data.
              */
             s->pbits = 512 << ((s->nbits - 1) / 64);
             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);
             put_uint32(pktout, s->pbits); /* min */
             put_uint32(pktout, s->pbits); /* preferred */
@@ -315,14 +325,19 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
         } else {
             s->dh_ctx = dh_setup_group(s->kex_alg);
             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. */
         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)
             ppl_logevent("%s", s->shgss->lib->gsslogmsg);
@@ -386,7 +401,11 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
                 }
                 put_string(pktout,
                            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);
                 s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok);
                 ppl_logevent("GSSAPI key exchange initialised");
@@ -413,7 +432,11 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
                 continue;
               case SSH2_MSG_KEXGSS_COMPLETE:
                 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);
                 s->mic.value = (char *)data.ptr;
                 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->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);
             if (err) {
                 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;
                 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
          * involve user interaction. */
@@ -493,29 +527,42 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
 
         if (!s->hkey)
             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
          * 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
     } else {
@@ -586,15 +633,21 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
             strbuf *buf, *outstr;
 
             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);
 
             /*
              * Encode this as an mpint.
              */
             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.
@@ -643,7 +696,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
     ssh2transport_finalise_exhash(s);
 
 #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_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"
          * 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
          * 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;
 
     /* 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) {
             ssh_proto_error(s->ppl.ssh, "Server's host key is invalid");
             *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
     if (s->gss_kex_used) {
         /*
          * In a GSS-based session, check the host key (if any) against
          * 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
              * host key, store it.
              */
             if (s->hkey) {
-                char *fingerprint = ssh2_fingerprint(
+                char *fingerprint = ssh2_double_fingerprint(
                     s->hkey, SSH_FPTYPE_DEFAULT);
                 ppl_logevent("GSS kex provided fallback host key:");
                 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
              * 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
-            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) {
                 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
                 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(
                     ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport,
                     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);
                 sfree(keydisp);
                 } // WINSCP
-            }
 #ifdef FUZZING
-            s->spr = SPR_OK;
+                s->spr = SPR_OK;
 #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
              * 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) {
             assert(s->hkey);
             assert(ssh_key_alg(s->hkey) == s->cross_certifying);
 
             { // 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("%s", 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
              * 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
         } 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
              * 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
                 ssh_sw_abort(s->ppl.ssh,
                              "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,
-                         const void *data, size_t length)
+                            const void *data, size_t length)
 {
     pinitassert(chan->vt == &mainchan_channelvt);
     mainchan *mc = container_of(chan, mainchan, chan);

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

@@ -9,38 +9,63 @@
 
 #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
      * 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
      * {iso(1) member-body(2) United States(840) mit(113554)
      * infosys(1) gssapi(2) generic(1) user_name(1)}.  The constant
      * 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 */
-    {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
      * {iso(1) member-body(2) United States(840) mit(113554)
      * infosys(1) gssapi(2) generic(1) machine_uid_name(2)}.
      * 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 */
-    {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
      * {iso(1) member-body(2) United States(840) mit(113554)
      * infosys(1) gssapi(2) generic(1) string_uid_name(3)}.
      * 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 */
-    {6, (void *)"\x2b\x06\x01\x05\x06\x02"},
+    6, "\x2b\x06\x01\x05\x06\x02",
     /* corresponding to an object-identifier value of
      * {iso(1) org(3) dod(6) internet(1) security(5)
      * 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
      * for GSS_C_NT_HOSTBASED_SERVICE when presented as an input
      * 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 */
-     {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)
      * member-body(2) Unites States(840) mit(113554) infosys(1)
      * gssapi(2) generic(1) service_name(4)}.  The constant
      * 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 */
-    {6, (void *)"\x2b\x06\01\x05\x06\x03"},
+    6, "\x2b\x06\01\x05\x06\x03",
     /* corresponding to an object identifier value of
      * {1(iso), 3(org), 6(dod), 1(internet), 5(security),
      * 6(nametypes), 3(gss-anonymous-name)}.  The constant
      * 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 */
-    {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),
      * 6(nametypes), 4(gss-api-exported-name)}.  The constant
      * GSS_C_NT_EXPORT_NAME should be initialized to point
      * 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 */
 
 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)*/
 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_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 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)

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

@@ -113,12 +113,13 @@ PacketProtocolLayer *ssh2_transport_new(
     const SshServerConfig *ssc);
 PacketProtocolLayer *ssh2_userauth_new(
     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,
-    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,
+    const char *auth_plugin,
     const char * loghost, bool change_password, Seat *seat); // WINSCP
 PacketProtocolLayer *ssh2_connection_new(
     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);
 }
 
-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);
     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;
     dummyhc.server_id = server_id;
@@ -559,9 +559,9 @@ static void share_remove_halfchannel(struct ssh_sharing_connstate *cs,
     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);
     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);
 }
 
-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;
     dummychan.upstream_id = upstream_id;
     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;
     dummychan.server_id = server_id;
@@ -626,9 +626,8 @@ static void share_remove_channel(struct ssh_sharing_connstate *cs,
     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);
     xc->upstream_id = upstream_id;
@@ -647,16 +646,16 @@ static struct share_xchannel *share_add_xchannel
     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;
     dummyxc.upstream_id = upstream_id;
     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;
     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,
-                                 struct share_xchannel *xc)
+                                  struct share_xchannel *xc)
 {
     del234(cs->xchannels_by_us, xc);
     del234(cs->xchannels_by_server, 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);
     fwd->host = dupstr(host);
@@ -687,8 +685,8 @@ static struct share_forwarding *share_add_forwarding
     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;
     dummyfwd.host = dupstr(host);
@@ -989,8 +987,8 @@ static void share_xchannel_add_message(
     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
@@ -1013,10 +1011,10 @@ void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs,
             if (get_bool(src)) {
                 strbuf *packet = strbuf_new();
                 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);
             }
         } 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;
 
@@ -1072,8 +1069,8 @@ void share_xchannel_confirmation(struct ssh_sharing_connstate *cs,
     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
@@ -1382,9 +1379,9 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs,
                  * cleaned up if downstream goes away.
                  */
                 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);
                 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.
                  */
                 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);
 
                 /*
@@ -1996,7 +1993,7 @@ static int share_listen_accepting(Plug *plug,
  * configurations which return the same string from this function will
  * 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 *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);
 
                 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_detached_cert),
                     conf_get_bool(ssh->conf, CONF_ssh_show_banner),
                     conf_get_bool(ssh->conf, CONF_tryagent),
                     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_kex),
                     conf_get_bool(ssh->conf, CONF_gssapifwd),
-                    &ssh->gss_state
+                    &ssh->gss_state,
 #else
                     false,
                     false,
                     false,
-                    NULL
+                    NULL,
 #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
                     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 ca_blob_compare(void *av, void *bv);
 
 PacketProtocolLayer *ssh2_transport_new(
     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->stats = stats;
     s->hostkeyblob = strbuf_new();
+    s->host_cas = newtree234(ca_blob_compare);
 
     pq_in_init(&s->pq_in_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->server_greeting);
     sfree(s->keystr);
-    sfree(s->hostkey_str);
     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) {
         ssh_key_free(s->hkey);
         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->p) mp_free(s->p);
     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)
         dh_cleanup(s->dh_ctx);
     if (s->rsa_kex_key_needs_freeing) {
@@ -234,7 +245,7 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl)
         sfree(s->rsa_kex_key);
     }
     if (s->ecdh_key)
-        ssh_ecdhkex_freekey(s->ecdh_key);
+        ecdh_key_free(s->ecdh_key);
     if (s->exhash)
         ssh_hash_free(s->exhash);
     strbuf_free(s->outgoing_kexinit);
@@ -252,7 +263,7 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl)
  */
 static void ssh2_mkkey(
     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 keylen_padded;
@@ -280,7 +291,7 @@ static void ssh2_mkkey(
     /* First hlen bytes. */
     h = ssh_hash_new(s->kex_alg->hash);
     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_byte(h, chr);
     put_data(h, s->session_id, s->session_id_len);
@@ -292,7 +303,7 @@ static void ssh2_mkkey(
 
         ssh_hash_reset(h);
         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);
 
         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.
  * If the algorithm is already in the list, return a pointer to its
  * 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)
@@ -491,10 +507,10 @@ PktIn *ssh2_transport_pop(struct ssh2_transport_state *s)
 
 static void ssh2_write_kexinit_lists(
     /*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,
     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,
     bool first_time, bool can_gssapi_keyex, bool transient_hostkey_mode)
 {
@@ -502,7 +518,7 @@ static void ssh2_write_kexinit_lists(
     bool warn;
 
     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 preferred_hk[HK_MAX];
     int n_preferred_ciphers;
@@ -517,14 +533,33 @@ static void ssh2_write_kexinit_lists(
      * Set up the preferred key exchange. (NULL => warn below here)
      */
     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;
+    }
     for (i = 0; i < KEX_MAX; i++) {
         switch (conf_get_int_int(conf, CONF_ssh_kexlist, i)) {
           case KEX_DHGEX:
             preferred_kex[n_preferred_kex++] =
                 &ssh_diffiehellman_gex;
             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:
             preferred_kex[n_preferred_kex++] =
                 &ssh_diffiehellman_group14;
@@ -541,6 +576,10 @@ static void ssh2_write_kexinit_lists(
             preferred_kex[n_preferred_kex++] =
                 &ssh_ecdh_kex;
             break;
+          case KEX_NTRU_HYBRID:
+            preferred_kex[n_preferred_kex++] =
+                &ssh_ntru_hybrid_kex;
+            break;
           case KEX_WARN:
             /* Flag for later. Don't bother if it's the last in
              * the list. */
@@ -590,6 +629,9 @@ static void ssh2_write_kexinit_lists(
           case CIPHER_CHACHA20:
             preferred_ciphers[n_preferred_ciphers++] = &ssh2_ccp;
             break;
+          case CIPHER_AESGCM:
+            preferred_ciphers[n_preferred_ciphers++] = &ssh2_aesgcm;
+            break;
           case CIPHER_WARN:
             /* Flag for later. Don't bother if it's the last in
              * the list. */
@@ -609,15 +651,14 @@ static void ssh2_write_kexinit_lists(
         preferred_comp = &ssh_comp_none;
 
     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. */
     warn = false;
     for (i = 0; i < n_preferred_kex; i++) {
         const ssh_kexes *k = preferred_kex[i];
         if (!k) warn = true;
         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);
                 alg->u.kex.kex = k->list[j];
                 alg->u.kex.warn = warn;
@@ -632,23 +673,27 @@ static void ssh2_write_kexinit_lists(
         for (i = 0; i < our_nhostkeys; 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);
             alg->u.hk.hostkey = keyalg;
             alg->u.hk.hkflags = 0;
             alg->u.hk.warn = false;
 
-            if (keyalg == &ssh_rsa) {
-                alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY],
-                                          "rsa-sha2-256");
-                alg->u.hk.hostkey = keyalg;
-                alg->u.hk.hkflags = SSH_AGENT_RSA_SHA2_256;
-                alg->u.hk.warn = false;
+            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.hkflags = SSH_AGENT_RSA_SHA2_512;
+                alg->u.hk.hkflags = try_flags[i];
                 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'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;
         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++) {
-                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;
                 if (conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys) &&
                     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;
                 }
             }
         }
+
+        /* And finally, everything else */
         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++) {
-                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;
-                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;
             }
         }
@@ -719,7 +822,7 @@ static void ssh2_write_kexinit_lists(
                     continue;
                 if (ssh_transient_hostkey_cache_has(
                         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);
                     alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg;
                     alg->u.hk.warn = warn;
@@ -736,19 +839,19 @@ static void ssh2_write_kexinit_lists(
          * reverification.
          */
         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.warn = false;
     }
     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;
     }
     /* List encryption algorithms (client->server then server->client). */
     for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) {
         warn = false;
 #ifdef FUZZING
-        alg = ssh2_kexinit_addalg(kexlists[k], "none");
+        alg = ssh2_kexinit_addalg(&kexlists[K], "none");
         alg->u.cipher.cipher = NULL;
         alg->u.cipher.warn = warn;
 #endif /* FUZZING */
@@ -756,7 +859,7 @@ static void ssh2_write_kexinit_lists(
             const ssh2_ciphers *c = preferred_ciphers[i];
             if (!c) warn = true;
             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);
                     alg->u.cipher.cipher = c->list[j];
                     alg->u.cipher.warn = warn;
@@ -783,7 +886,7 @@ static void ssh2_write_kexinit_lists(
         alg->u.mac.etm = false;
 #endif /* FUZZING */
         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.etm = false;
         }
@@ -791,7 +894,7 @@ static void ssh2_write_kexinit_lists(
             /* For each MAC, there may also be an ETM version,
              * which we list second. */
             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.etm = true;
             }
@@ -804,22 +907,22 @@ static void ssh2_write_kexinit_lists(
     for (j = KEXLIST_CSCOMP; j <= KEXLIST_SCCOMP; j++) {
         assert(lenof(compressions) > 1);
         /* 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.delayed = false;
         if (preferred_comp->delayed_name) {
-            alg = ssh2_kexinit_addalg(kexlists[j],
+            alg = ssh2_kexinit_addalg(&kexlists[j],
                                       preferred_comp->delayed_name);
             alg->u.comp.comp = preferred_comp;
             alg->u.comp.delayed = true;
         }
         for (i = 0; i < lenof(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.delayed = false;
             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.delayed = true;
             }
@@ -837,10 +940,8 @@ static void ssh2_write_kexinit_lists(
             put_datapl(list, ssc->kex_override[i]);
         } else {
         #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
         }
         #endif
@@ -858,14 +959,19 @@ static void ssh2_write_kexinit_lists(
     put_stringz(pktout, "");
 }
 
+struct server_hostkeys {
+    int *indices;
+    size_t n, size;
+};
+
 static bool ssh2_scan_kexinits(
     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,
     transport_direction *cs, transport_direction *sc,
     bool *warn_kex, bool *warn_hk, bool *warn_cscipher, bool *warn_sccipher,
     Ssh *ssh, bool *ignore_guess_cs_packet, bool *ignore_guess_sc_packet,
-    int *n_server_hostkeys, int server_hostkeys[MAXKEXLIST], unsigned *hkflags,
+    struct server_hostkeys *server_hostkeys, unsigned *hkflags,
     bool *can_send_ext_info)
 {
     BinarySource client[1], server[1];
@@ -930,10 +1036,9 @@ static bool ssh2_scan_kexinits(
       found_match:
 
         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;
             }
         }
@@ -1089,15 +1194,15 @@ static bool ssh2_scan_kexinits(
          * one or not. We return these as a list of indices into the
          * constant ssh2_hostkey_algs[] array.
          */
-        *n_server_hostkeys = 0;
-
         { // WINSCP
         ptrlen list = slists[KEXLIST_HOSTKEY];
         ptrlen word; // WINSCP
         for (; get_commasep_word(&list, &word) ;) {
             for (i = 0; i < lenof(ssh2_hostkey_algs); i++)
                 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;
                 }
         }
@@ -1107,9 +1212,95 @@ static bool ssh2_scan_kexinits(
     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)
 {
-    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));
     ssh_hash_final(s->exhash, s->exchange_hash);
     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
      * later.
      */
-    strbuf_clear(s->client_kexinit);
+    strbuf_clear(s->outgoing_kexinit);
     put_byte(s->outgoing_kexinit, SSH2_MSG_KEXINIT);
     random_read(strbuf_append(s->outgoing_kexinit, 16), 16);
     ssh2_write_kexinit_lists(
         /*WINSCP*/ s->ppl.seat, BinarySink_UPCAST(s->outgoing_kexinit), s->kexlists,
         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->got_session_id, s->can_gssapi_keyex,
         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 */
 
     /*
-     * 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.
@@ -1242,21 +1455,35 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
     put_byte(s->incoming_kexinit, SSH2_MSG_KEXINIT);
     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
      * selected algorithm identifiers.
      */
     {
-        int nhk, hks[MAXKEXLIST], i, j;
+        struct server_hostkeys hks = { NULL, 0, 0 };
 
         if (!ssh2_scan_kexinits(
                 ptrlen_from_strbuf(s->client_kexinit),
                 ptrlen_from_strbuf(s->server_kexinit),
                 s->kexlists, &s->kex_alg, &s->hostkey_alg, s->cstrans,
                 s->sctrans, &s->warn_kex, &s->warn_hk, &s->warn_cscipher,
-                &s->warn_sccipher, s->ppl.ssh, NULL, &s->ignorepkt, &nhk, hks,
-                &s->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 */
+        }
 
         /*
          * 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;
 
-        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 &&
+                ssh2_hostkey_algs[j].alg->cache_id &&
                 !have_ssh_host_key(s->ppl.seat, s->savedhost, s->savedport, // WINSCP
                                    ssh2_hostkey_algs[j].alg->cache_id)) {
                 s->uncert_hostkeys[s->n_uncert_hostkeys++] = j;
             }
         }
+
+        sfree(hks.indices);
     }
 
     if (s->warn_kex) {
@@ -1379,6 +1609,9 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
      * Actually perform the key exchange.
      */
     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->server_greeting);
     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();
 
         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);
-            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,
                        s->out.cipher->padded_keybytes);
         }
         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);
         }
 
@@ -1527,14 +1760,14 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
         strbuf *mac_key = strbuf_new_nm();
 
         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);
-            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,
                        s->in.cipher->padded_keybytes);
         }
         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);
         }
 
@@ -1552,7 +1785,8 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
     /*
      * 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
@@ -2123,9 +2357,9 @@ static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
     for (i = 0; i < CIPHER_MAX; i++)
         if (conf_get_int_int(s->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) !=
         conf_get_bool(conf, CONF_ssh2_des_cbc)) {
         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;
 }
 
+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
  * 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_MAX_SIZE 8192
 
-#define MAXKEXLIST 16
 struct kexinit_algorithm {
-    const char *name;
+    ptrlen name;
     union {
         struct {
             const ssh_kex *kex;
@@ -45,18 +44,31 @@ struct kexinit_algorithm {
         } comp;
     } 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() */ \
-    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_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 */
 #define COUNT_HOSTKEY_ALGORITHM(type, alg) +1
 #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_keyalg *hostkey_alg;
-    char *hostkey_str; /* string representation, for easy checking in rekeys */
     unsigned char session_id[MAX_HASH_LEN];
     int session_id_len;
     int dh_min_size, dh_max_size;
@@ -143,7 +154,7 @@ struct ssh2_transport_state {
 
     char *client_greeting, *server_greeting;
 
-    bool kex_in_progress;
+    bool kex_in_progress, kexinit_delayed;
     unsigned long next_rekey, last_rekey;
     const char *deferred_rekey_reason;
     bool higher_layer_ok;
@@ -166,15 +177,20 @@ struct ssh2_transport_state {
 
     bool gss_kex_used;
 
+    tree234 *host_cas;
+
     int nbits, pbits;
     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 *client_kexinit, *server_kexinit; /* aliases to the above */
     int kex_init_value, kex_reply_value;
     transport_direction in, out, *cstrans, *sctrans;
     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;
     ssh_key *hkey;                     /* actual host key */
     unsigned hkflags;                  /* signing flags, used in server */
@@ -190,7 +206,7 @@ struct ssh2_transport_state {
     SeatPromptResult spr;
     bool guessok;
     bool ignorepkt;
-    struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST];
+    struct kexinit_algorithm_list kexlists[NKEXLIST];
 #ifndef NO_GSSAPI
     Ssh_gss_buf gss_buf;
     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 PktOut *ssh_verstring_new_pktout(int type);
 static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp,
-                                          const char *msg, int category);
+                                           const char *msg, int category);
 
 static const BinaryPacketProtocolVtable ssh_verstring_vtable = {
     // WINSCP
@@ -310,8 +310,8 @@ void ssh_verstring_handle_input(BinaryPacketProtocol *bpp)
      * a NUL terminator.
      */
     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);
 
     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 "
                      "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)

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

@@ -54,8 +54,8 @@ struct LZ77InternalContext;
 struct LZ77Context {
     struct LZ77InternalContext *ictx;
     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 *zlib_compress_init(void)
+static ssh_compressor *zlib_compress_init(void)
 {
     struct Outbuf *out;
     struct ssh_zlib_compressor *comp = snew(struct ssh_zlib_compressor);
@@ -592,7 +592,7 @@ ssh_compressor *zlib_compress_init(void)
     return &comp->sc;
 }
 
-void zlib_compress_cleanup(ssh_compressor *sc)
+static void zlib_compress_cleanup(ssh_compressor *sc)
 {
     struct ssh_zlib_compressor *comp =
         container_of(sc, struct ssh_zlib_compressor, sc);
@@ -604,10 +604,9 @@ void zlib_compress_cleanup(ssh_compressor *sc)
     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 =
         container_of(sc, struct ssh_zlib_compressor, sc);
@@ -904,7 +903,7 @@ struct zlib_decompress_ctx {
     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);
     unsigned char lengths[288];
@@ -927,7 +926,7 @@ ssh_decompressor *zlib_decompress_init(void)
     return &dctx->dc;
 }
 
-void zlib_decompress_cleanup(ssh_decompressor *dc)
+static void zlib_decompress_cleanup(ssh_decompressor *dc)
 {
     struct zlib_decompress_ctx *dctx =
         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,
-                   struct zlib_table *tab)
+                          struct zlib_table *tab)
 {
     unsigned long bits = *bitsp;
     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) )
 
-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 =
         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) {
                 dctx->currlentable = zlib_mktable(dctx->lengths, dctx->hlit);
                 dctx->currdisttable = zlib_mktable(dctx->lengths + dctx->hlit,
-                                                  dctx->hdist);
+                                                   dctx->hdist);
                 zlib_freetable(&dctx->lenlentable);
                 dctx->lenlentable = NULL;
                 dctx->state = INBLK;
@@ -1112,7 +1111,7 @@ bool zlib_decompress_block(ssh_decompressor *dc,
                 dctx->lenextrabits = (code == 16 ? 2 : code == 17 ? 3 : 7);
                 dctx->lenaddon = (code == 18 ? 11 : 3);
                 dctx->lenrep = (code == 16 && dctx->lenptr > 0 ?
-                               dctx->lengths[dctx->lenptr - 1] : 0);
+                                dctx->lengths[dctx->lenptr - 1] : 0);
                 dctx->state = TREES_LENREP;
             }
             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)
             goto end;
 
-        buf = strbuf_new_nm();
-        put_datapl(buf, get_data(src, enclen));
+        buf = strbuf_dup_nm(get_data(src, enclen));
 
         { // WINSCP
         unsigned char keybuf[16];
@@ -619,6 +618,14 @@ const ssh_keyalg *const all_keyalgs[] = {
     &ssh_ecdsa_nistp521,
     &ssh_ecdsa_ed25519,
     &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);
 
@@ -637,6 +644,18 @@ const ssh_keyalg *find_pubkey_alg(const char *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 {
     const char *name;
     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);
         return ret;
     } 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;
     }
 
@@ -1303,7 +1322,7 @@ bool ppk_loadpub_s(BinarySource *src, char **algorithm, BinarySink *bs,
         if (0 == strncmp(header, "PuTTY-User-Key-File-", 20))
             error = "PuTTY key format too new";
         else
-            error = "not a PuTTY SSH-2 private key";
+            error = "not a public key or a PuTTY SSH-2 private key";
         goto error;
     }
     error = "file format error";
@@ -1449,37 +1468,6 @@ int base64_lines(int datalen)
     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 = {
     // WINSCP
     /*.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, "Comment: %s\n", key->comment);
     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) {
         put_fmt(out, "Key-Derivation: %s\n",
                 params.argon2_flavour == Argon2d ? "Argon2d" :
@@ -1648,8 +1636,8 @@ strbuf *ppk_save_sb(ssh2_userkey *key, const char *passphrase,
         } // WINSCP
     }
     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: ");
     for (i = 0; i < macalg->len; 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)
 {
     strbuf *sb = strbuf_new();
+    strbuf *tmp = NULL;
 
     /*
      * Identify the key algorithm, if possible.
@@ -1866,24 +1855,63 @@ char *ssh2_fingerprint_blob(ptrlen blob, FingerprintType fptype)
         if (alg) {
             int bits = ssh_key_public_bits(alg, blob);
             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 {
             put_fmt(sb, "%.*s ", PTRLEN_PRINTF(algname));
         }
     }
     } // WINSCP
 
-    switch (fptype) {
+    switch (ssh_fptype_from_cert(fptype)) {
       case SSH_FPTYPE_MD5:
         ssh2_fingerprint_blob_md5(blob, sb);
         break;
       case SSH_FPTYPE_SHA256:
         ssh2_fingerprint_blob_sha256(blob, sb);
         break;
+      default:
+        unreachable("ssh_fptype_from_cert ruled out the other values");
     }
 
+    if (tmp)
+        strbuf_free(tmp);
+
     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 **fps = snewn(SSH_N_FPTYPES, char *);
@@ -1903,6 +1931,15 @@ char *ssh2_fingerprint(ssh_key *data, FingerprintType fptype)
     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)
 {
     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 &&
         get_chars(src, " ").len == 1 &&
         get_chars(src, "0123456789ABCDEFGHIJKLMNOPQRSTUV"
-                   "WXYZabcdefghijklmnopqrstuvwxyz+/=").len > 0 &&
+                  "WXYZabcdefghijklmnopqrstuvwxyz+/=").len > 0 &&
         get_nonchars(src, " \n").len == 0)
         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");
     }
 }
-
-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
 #define PUTTY_STORAGE_H
 
+#include "defs.h"
+
 /* ----------------------------------------------------------------------
  * Functions to save and restore PuTTY sessions. Note that this is
  * 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,
                     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.
  */

+ 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) {}
 SeatPromptResult nullseat_confirm_ssh_host_key(
     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)
 { return SPR_SW_ABORT("this seat can't handle interactive prompts"); }
 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_yes(Seat *seat) { return true; }
 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).
  */
-void freetree234(tree234 * t);
+void freetree234(tree234 *t);
 
 /*
  * 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.
  */
-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
@@ -61,7 +61,7 @@ void *add234(tree234 * t, void *e);
  * Index range can be from 0 to the tree's current element count,
  * 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.
@@ -81,7 +81,7 @@ void *addpos234(tree234 * t, void *e, int index);
  *       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
@@ -126,10 +126,10 @@ void *index234(tree234 * t, int index);
 enum {
     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);
 
 /*
@@ -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
  * 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.
  */
-int count234(tree234 * t);
+int count234(tree234 *t);
 
 #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 *subkeyin, char **subkeyout)
+                        char *subkeyin, char **subkeyout)
 {
     struct constkey key;
     struct conf_entry *entry;
@@ -476,7 +476,7 @@ void conf_del_str_str(Conf *conf, int primary, const char *secondary)
         del234(conf->tree, entry);
         free_entry(entry);
     }
- }
+}
 
 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)
 {
     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,
@@ -61,7 +67,7 @@ void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb,
                 endpos--;
             { // WINSCP
             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);
             sfree(msg);
 
@@ -78,7 +84,8 @@ void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb,
          */
         if (pos == 0 && psb->size == lenof(psb->buf)) {
             char *msg = dupprintf(
-                "proxy (partial line): %.*s", (int)psb->size, psb->buf);
+                "%s (partial line): %.*s", psb->prefix, (int)psb->size,
+                psb->buf);
             plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0);
             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;
 }
 
+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)
 {
     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 "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 *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);
     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);
 }
 
+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
  * 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(
     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)
 {
     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_weak_crypto_primitive =*/ tempseat_confirm_weak_crypto_primitive,
     /*.confirm_weak_cached_hostkey =*/ tempseat_confirm_weak_cached_hostkey,
+    .prompt_descriptions = tempseat_prompt_descriptions,
     /*.is_utf8 =*/ tempseat_is_utf8,
     /*.echoedit_update =*/ tempseat_echoedit_update,
     /*.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).
  */
-static void freenode234(node234 * n)
+static void freenode234(node234 *n)
 {
     if (!n)
         return;
@@ -84,7 +84,7 @@ static void freenode234(node234 * n)
     sfree(n);
 }
 
-void freetree234(tree234 * t)
+void freetree234(tree234 *t)
 {
     freenode234(t->root);
     sfree(t);
@@ -93,7 +93,7 @@ void freetree234(tree234 * t)
 /*
  * Internal function to count a node.
  */
-static int countnode234(node234 * n)
+static int countnode234(node234 *n)
 {
     int count = 0;
     int i;
@@ -122,7 +122,7 @@ static int elements234(node234 *n)
 /*
  * Count the elements in a tree.
  */
-int count234(tree234 * t)
+int count234(tree234 *t)
 {
     if (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
  * 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;
     void *orig_e = e;
@@ -463,14 +463,14 @@ static void *add234_internal(tree234 * t, void *e, int index)
     return orig_e;
 }
 
-void *add234(tree234 * t, void *e)
+void *add234(tree234 *t, void *e)
 {
     if (!t->cmp)                       /* tree is unsorted */
         return NULL;
 
     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 */
         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.
  * Returns NULL if the index is out of range.
  */
-void *index234(tree234 * t, int index)
+void *index234(tree234 *t, int index)
 {
     node234 *n;
 
@@ -523,7 +523,7 @@ void *index234(tree234 * t, int index)
  * as NULL, in which case the compare function from the tree proper
  * will be used.
  */
-void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp,
+void *findrelpos234(tree234 *t, void *e, cmpfn234 cmp,
                     int relation, int *index)
 {
     search234_state ss;
@@ -598,15 +598,15 @@ void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp,
         *index = ss.index;
     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);
 }
-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);
 }
-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);
 }
@@ -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,
  * 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;
     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))
         return NULL;
     return delpos234_internal(t, index);
 }
-void *del234(tree234 * t, void *e)
+void *del234(tree234 *t, void *e)
 {
     int index;
     if (!findrelpos234(t, e, NULL, REL234_EQ, &index))
@@ -1095,7 +1095,7 @@ typedef struct {
     int elemcount;
 } chkctx;
 
-int chknode(chkctx * ctx, int level, node234 * node,
+int chknode(chkctx *ctx, int level, node234 *node,
             void *lowbound, void *highbound)
 {
     int nkids, nelems;

+ 4 - 4
source/putty/version.h

@@ -1,5 +1,5 @@
 /* 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)
                 LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
             if (psd) {
-                if (p_InitializeSecurityDescriptor
-                    (psd, SECURITY_DESCRIPTOR_REVISION) &&
+                if (p_InitializeSecurityDescriptor(
+                        psd, SECURITY_DESCRIPTOR_REVISION) &&
                     p_SetSecurityDescriptorOwner(psd, usersid, false)) {
                     sa.nLength = sizeof(sa);
                     sa.bInheritHandle = true;

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

@@ -94,8 +94,6 @@ static
 #endif
 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 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->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, FreeContextBuffer);
         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;
 
     winctx->maj_stat = p_VerifySignature(&winctx->context,
-                                       &InputBufferDescriptor,
-                                       0, &qop);
+                                         &InputBufferDescriptor,
+                                         0, &qop);
     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;
 }
 
+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)
 {
     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;
     } // 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
 #include <ws2tcpip.h>
 
+#if HAVE_AFUNIX_H
+#include <afunix.h>
+#endif
+
 #ifndef NO_IPV6
 #ifdef __clang__
 #pragma clang diagnostic push
@@ -83,12 +87,28 @@ struct NetSocket {
     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 {
     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
     struct addrinfo *ais;              /* Addresses IPv6 style. */
 #endif
@@ -99,18 +119,27 @@ struct SockAddr {
 
 /*
  * 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
-#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
+      default:
+        return AF_UNSPEC;
+    }
+}
 
 /*
  * 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, WSACleanup, (void));
 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, struct hostent FAR *, gethostbyname,
                       (const char FAR *));
 DECL_WINDOWS_FUNCTION(static, struct servent FAR *, getservbyname,
                       (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, const char FAR *, inet_ntop,
                       (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, shutdown, (SOCKET, int));
 DECL_WINDOWS_FUNCTION(static, int, ioctlsocket,
-                      (SOCKET, long, u_long FAR *));
+                      (SOCKET, LONG, ULONG FAR *));
 DECL_WINDOWS_FUNCTION(static, SOCKET, accept,
                       (SOCKET, struct sockaddr FAR *, int FAR *));
 DECL_WINDOWS_FUNCTION(static, int, getpeername,
@@ -199,10 +228,9 @@ DECL_WINDOWS_FUNCTION(static, int, getaddrinfo,
                        const struct addrinfo *hints, struct addrinfo **res));
 DECL_WINDOWS_FUNCTION(static, void, freeaddrinfo, (struct addrinfo *res));
 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));
-DECL_WINDOWS_FUNCTION(static, char *, gai_strerror, (int ecode));
 DECL_WINDOWS_FUNCTION(static, int, WSAAddressToStringA,
                       (LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFO,
                        LPSTR, LPDWORD));
@@ -250,7 +278,6 @@ void sk_init(void)
     if (!winsock_module)
     {
         modalfatalbox("Unable to load any WinSock library");
-
     }
 
 #ifndef NO_IPV6
@@ -262,7 +289,6 @@ void sk_init(void)
         /* This function would fail its type-check if we did one,
          * because the VS header file provides an inline definition
          * which is __cdecl instead of WINAPI. */
-        GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gai_strerror);
     } else {
         /* Fall back to wship6.dll for Windows 2000 */
         wship6_module = load_system32_dll("wship6.dll");
@@ -271,7 +297,6 @@ void sk_init(void)
             GET_WINDOWS_FUNCTION(wship6_module, freeaddrinfo);
             /* See comment above about type check */
             GET_WINDOWS_FUNCTION_NO_TYPECHECK(wship6_module, getnameinfo);
-            GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gai_strerror);
         } else {
         }
     }
@@ -290,10 +315,12 @@ void sk_init(void)
     GET_WINDOWS_FUNCTION(winsock_module, WSAStartup);
     GET_WINDOWS_FUNCTION(winsock_module, WSACleanup);
     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(winsock_module, gethostbyname);
     GET_WINDOWS_FUNCTION(winsock_module, getservbyname);
@@ -442,162 +469,130 @@ const char *winsock_error_string(int 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,
                         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
-    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
-                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
-                          "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 {
-        /*
-         * 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);
     ret->error = NULL;
-    ret->resolved = false;
+    ret->superfamily = superfamily;
 #ifndef NO_IPV6
     ret->ais = NULL;
 #endif
-    ret->namedpipe = false;
     ret->addresses = NULL;
     ret->naddresses = 0;
     ret->refcount = 1;
-    strncpy(ret->hostname, host, lenof(ret->hostname));
+    strncpy(ret->hostname, name, lenof(ret->hostname));
     ret->hostname[lenof(ret->hostname)-1] = '\0';
     return ret;
 }
 
+SockAddr *sk_nonamelookup(const char *host)
+{
+    return sk_special_addr(UNRESOLVED, host);
+}
+
 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)
 {
 #ifndef NO_IPV6
@@ -639,7 +634,7 @@ void sk_getaddr(SockAddr *addr, char *buf, int buflen)
         }
     } else
 #endif
-    if (SOCKADDR_FAMILY(addr, step) == AF_INET) {
+    if (sockaddr_family(addr, step) == AF_INET) {
         struct in_addr a;
         assert(addr->addresses && step.curraddr < addr->naddresses);
         a.s_addr = p_htonl(addr->addresses[step.curraddr]);
@@ -670,7 +665,7 @@ static SockAddr sk_extractaddr_tmp(
 #ifndef NO_IPV6
     toret.ais = step->ai;
 #endif
-    if (SOCKADDR_FAMILY(addr, *step) == AF_INET
+    if (sockaddr_family(addr, *step) == AF_INET
 #ifndef NO_IPV6
         && !toret.ais
 #endif
@@ -682,7 +677,11 @@ static SockAddr sk_extractaddr_tmp(
 
 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)
@@ -730,7 +729,7 @@ bool sk_address_is_local(SockAddr *addr)
     SockAddrStep step;
     int family;
     START_STEP(addr, step);
-    family = SOCKADDR_FAMILY(addr, step);
+    family = sockaddr_family(addr, step);
 
 #ifndef NO_IPV6
     if (family == AF_INET6) {
@@ -766,7 +765,7 @@ int sk_addrtype(SockAddr *addr)
     SockAddrStep step;
     int family;
     START_STEP(addr, step);
-    family = SOCKADDR_FAMILY(addr, step);
+    family = sockaddr_family(addr, step);
 
     return (family == AF_INET ? ADDRTYPE_IPV4 :
 #ifndef NO_IPV6
@@ -780,7 +779,7 @@ void sk_addrcopy(SockAddr *addr, char *buf)
     SockAddrStep step;
     int family;
     START_STEP(addr, step);
-    family = SOCKADDR_FAMILY(addr, step);
+    family = sockaddr_family(addr, step);
 
     assert(family != AF_UNSPEC);
 #ifndef NO_IPV6
@@ -941,7 +940,7 @@ static DWORD try_connect(NetSocket *sock,
     /*
      * 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
@@ -960,7 +959,7 @@ static DWORD try_connect(NetSocket *sock,
         goto ret;
     }
 
-        SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0);
+    SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0);
 
     if (sock->oobinline) {
         BOOL b = true;
@@ -1157,7 +1156,7 @@ static DWORD try_connect(NetSocket *sock,
 
     err = 0;
 
-    ret:
+  ret:
 
     /*
      * 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;
 }
 
-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;
+    SOCKADDR_IN a;
 #ifndef NO_IPV6
     SOCKADDR_IN6 a6;
 #endif
-    SOCKADDR_IN a;
+#if HAVE_AFUNIX_H
+    SOCKADDR_UN au;
+#endif
+    struct sockaddr *bindaddr;
+    unsigned bindsize;
 
     DWORD err;
     const char *errstr;
     NetSocket *ret;
     int retcode;
 
-    int address_family;
+    int address_family = orig_address_family;
 
     /*
      * Create NetSocket structure.
@@ -1260,16 +1265,6 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
     ret->parent = ret->child = 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
      * 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;
 
+#if HAVE_AFUNIX_H
+    if (address_family != AF_UNIX)
+#endif
     {
         BOOL on = true;
         p_setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
                      (const char *)&on, sizeof(on));
     }
 
+    switch (address_family) {
 #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
-        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) {
         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
      * 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) {
             NetSocket *ns = container_of(other, NetSocket, sock);
@@ -1427,6 +1437,33 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
     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)
 {
     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
  * writable.
  */
-void try_send(NetSocket *s)
+static void try_send(NetSocket *s)
 {
     while (s->sending_oob || bufchain_size(&s->output_data) > 0) {
         int nsent;
@@ -1780,7 +1817,7 @@ void select_result(WPARAM wParam, LPARAM lParam)
 #ifdef NO_IPV6
         struct sockaddr_in isa;
 #else
-        struct sockaddr_storage isa;
+        struct sockaddr_storage isa; // FIXME: also if Unix and no IPv6
 #endif
         int addrlen = sizeof(isa);
         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);
         if (t == INVALID_SOCKET)
         {
-          err = p_WSAGetLastError();
-          if (err == WSATRY_AGAIN)
-              break;
+            err = p_WSAGetLastError();
+            if (err == WSATRY_AGAIN)
+                break;
         }
 
         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))
 #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)) {
-          p_closesocket(t);      /* denied or error */
+            p_closesocket(t);      /* denied or error */
         }
         break;
       }
@@ -1836,7 +1873,7 @@ static SocketPeerInfo *sk_net_peer_info(Socket *sock)
 #ifdef NO_IPV6
     struct sockaddr_in addr;
 #else
-    struct sockaddr_storage addr;
+    struct sockaddr_storage addr; // FIXME: also if Unix and no IPv6
     char buf[INET6_ADDRSTRLEN];
 #endif
     int addrlen = sizeof(addr);
@@ -1963,7 +2000,7 @@ SockAddr *platform_get_x11_unix_address(const char *display, int displaynum)
 {
     SockAddr *ret = snew(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;
     return ret;
 }

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

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

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

@@ -37,6 +37,14 @@
 #define BUILDINFO_PLATFORM "Windows"
 #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
  * being a Pageant transaction */
 #define AGENT_COPYDATA_ID 0x804e50ba
@@ -114,9 +122,13 @@ static inline uintmax_t strtoumax(const char *nptr, char **endptr, int base)
 #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__
 #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(
     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);
 SeatPromptResult win_seat_confirm_weak_crypto_primitive(
     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(
     Seat *seat, const char *algname, const char *betteralgs,
     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,
@@ -302,6 +315,7 @@ void socket_reselect_all(void);
 SockAddr *sk_namedpipe_addr(const char *pipename);
 /* Turn a WinSock error code into a string. */
 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
@@ -310,9 +324,9 @@ const char *winsock_error_string(int error);
  * here they are.
  */
 DECL_WINDOWS_FUNCTION(extern, int, WSAAsyncSelect,
-                      (SOCKET, HWND, u_int, long));
+                      (SOCKET, HWND, u_int, LONG));
 DECL_WINDOWS_FUNCTION(extern, int, WSAEventSelect,
-                      (SOCKET, WSAEVENT, long));
+                      (SOCKET, WSAEVENT, LONG));
 DECL_WINDOWS_FUNCTION(extern, int, WSAGetLastError, (void));
 DECL_WINDOWS_FUNCTION(extern, int, WSAEnumNetworkEvents,
                       (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_clear_hwnd(void);
+void winselgui_response(WPARAM wParam, LPARAM lParam);
 
 void winselcli_setup(void);
 SOCKET winselcli_unique_socket(void);
@@ -356,6 +371,7 @@ Socket *make_deferred_handle_socket(DeferredSocketOpener *opener,
                                     SockAddr *addr, int port, Plug *plug);
 void setup_handle_socket(Socket *s, HANDLE send_H, HANDLE recv_H,
                          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_listener(const char *pipename, Plug *plug); /* winnps */
 
@@ -376,7 +392,7 @@ struct ctlpos {
     int ypos, width;
     int xoff;
     int boxystart, boxid;
-    char *boxtext;
+    const char *boxtext;
 };
 void init_common_controls(void);       /* also does some DLL-loading */
 
@@ -416,7 +432,7 @@ struct dlgparam {
     char *wintitle;                    /* title of actual window */
     char *errtitle;                    /* title of error sub-messageboxes */
     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 coloursel_wanted;             /* has an event handler asked for
                                         * a colour selector? */
@@ -435,56 +451,60 @@ struct dlgparam {
  */
 void ctlposinit(struct ctlpos *cp, HWND hwnd,
                 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 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 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);
-void staticddl(struct ctlpos *cp, char *stext,
+void staticddl(struct ctlpos *cp, const char *stext,
                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);
-void bigeditctrl(struct ctlpos *cp, char *stext,
+void bigeditctrl(struct ctlpos *cp, const char *stext,
                  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, ...);
-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,
-               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 *array, int maxmemb,
                      bool is_dlmsg, HWND hwnd,
                      WPARAM wParam, LPARAM lParam);
 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);
 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
 
 /*
- * 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.
  */
 struct winctrl {
-    union control *ctrl;
+    dlgcontrol *ctrl;
     /*
      * The control may have several components at the Windows
      * 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
- * 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.
  */
 struct winctrls {
@@ -541,7 +561,7 @@ void winctrl_init(struct winctrls *);
 void winctrl_cleanup(struct winctrls *);
 void winctrl_add(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_findbyindex(struct winctrls *, int);
 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
 
 /* 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 */
 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_value = "Recent 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 HMODULE shell32_module = NULL;
@@ -47,28 +48,16 @@ struct settings_w {
 
 settings_w *open_settings_w(const char *sessionname, char **errmsg)
 {
-    HKEY subkey1, sesskey;
-    int ret;
-    strbuf *sb;
-
     *errmsg = NULL;
 
     if (!sessionname || !*sessionname)
         sessionname = "Default Settings";
 
-    sb = strbuf_new();
+    strbuf *sb = strbuf_new();
     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"
                             "HKEY_CURRENT_USER\\%s\\%s", puttystr, sb->s);
         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)
 {
     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)
 {
     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)
 {
-    RegCloseKey(handle->sesskey);
+    close_regkey(handle->sesskey);
     sfree(handle);
 }
 
@@ -109,24 +96,12 @@ struct settings_r {
 
 settings_r *open_settings_r(const char *sessionname)
 {
-    HKEY subkey1, sesskey;
-    strbuf *sb;
-
     if (!sessionname || !*sessionname)
         sessionname = "Default Settings";
 
-    sb = strbuf_new();
+    strbuf *sb = strbuf_new();
     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);
 
     if (!sesskey)
@@ -141,42 +116,15 @@ settings_r *open_settings_r(const char *sessionname)
 
 char *read_setting_s(settings_r *handle, const char *key)
 {
-    DWORD type, allocsize, size;
-    char *ret;
-
     if (!handle)
         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)
 {
-    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;
     else
         return val;
@@ -259,25 +207,23 @@ void write_setting_filename(settings_w *handle,
 void close_settings_r(settings_r *handle)
 {
     if (handle) {
-        RegCloseKey(handle->sesskey);
+        close_regkey(handle->sesskey);
         sfree(handle);
     }
 }
 
 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;
 
-    sb = strbuf_new();
+    strbuf *sb = strbuf_new();
     escape_registry_key(sessionname, sb);
-    RegDeleteKey(subkey1, sb->s);
+    del_regkey(rkey, sb->s);
     strbuf_free(sb);
 
-    RegCloseKey(subkey1);
+    close_regkey(rkey);
 
     remove_session_from_jumplist(sessionname);
 }
@@ -289,13 +235,11 @@ struct settings_e {
 
 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;
 
-    ret = snew(settings_e);
+    settings_e *ret = snew(settings_e);
     if (ret) {
         ret->key = key;
         ret->i = 0;
@@ -306,30 +250,19 @@ settings_e *enum_settings_start(void)
 
 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++;
-    sfree(regbuf);
-    return success;
+    return true;
 }
 
 void enum_settings_finish(settings_e *e)
 {
-    RegCloseKey(e->key);
+    close_regkey(e->key);
     sfree(e);
 }
 
@@ -345,55 +278,37 @@ int retrieve_host_key(const char *hostname, int port,
                     const char *keytype, char *key, int maxlen)
 #else
 int check_stored_host_key(const char *hostname, int port,
-                    const char *keytype, const char *key)
+                          const char *keytype, const char *key)
 #endif
 {
-    char *otherstr;
-    strbuf *regname;
-    int len;
-    HKEY rkey;
-    DWORD readlen;
-    DWORD type;
-    int ret, compare;
-
 #ifdef MPEXT
     len = maxlen;
 #else
-    len = 1 + strlen(key);
 #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);
 
-    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);
         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
          * another trick, which is to look up the _old_ key format
          * under just the hostname and translate that.
          */
         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
              * 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
              * 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;
 
             for (i = 0; i < 2; i++) {
                 int ndigits, nwords;
-                *p++ = '0';
-                *p++ = 'x';
+                put_datapl(new, PTRLEN_LITERAL("0x"));
                 ndigits = strcspn(q, "/");      /* find / or end of string */
                 nwords = ndigits / 4;
                 /* now trim ndigits to remove leading zeros */
                 while (q[(ndigits - 1) ^ 3] == '0' && ndigits > 1)
                     ndigits--;
                 /* 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;
                 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
              * 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);
     }
 
-    RegCloseKey(rkey);
+    close_regkey(rkey);
 
 #ifdef MPEXT
     // 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);
     key[maxlen - 1] = '\0';
 #else
-    compare = strcmp(otherstr, key);
+    int compare = otherstr ? strcmp(otherstr, key) : -1;
 #endif
 
     sfree(otherstr);
     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 */
-    else
 #endif
-    if (ret != ERROR_SUCCESS || type != REG_SZ)
-        return 1;                      /* key does not exist in registry */
     else
         return 0;                      /* key matched OK in registry */
 }
 
 #ifndef MPEXT
 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.
@@ -488,22 +401,153 @@ bool have_ssh_host_key(const char *hostname, int port,
 void store_host_key(const char *hostname, int port,
                     const char *keytype, const char *key)
 {
-    strbuf *regname;
-    HKEY rkey;
-
-    regname = strbuf_new();
+    strbuf *regname = strbuf_new();
     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 */
 
     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.
  */
@@ -540,7 +584,6 @@ static bool try_random_seed_and_free(char *path, int action, HANDLE *hout)
 
 static HANDLE access_random_seed(int action)
 {
-    HKEY rkey;
     HANDLE rethandle;
 
     /*
@@ -559,16 +602,16 @@ static HANDLE access_random_seed(int action)
      * 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
  * 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;
-    }
 
     /* 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.
      */
+    bool write_failure = false;
     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. */
-        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
          * 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. */
-                settings_r *psettings_tmp = open_settings_r(piterator_old);
+                settings_r *psettings_tmp = open_settings_r(olditem);
                 if (psettings_tmp != NULL) {
                     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. */
-        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;
-    } else {
+    else
         return JUMPLISTREG_OK;
-    }
 }
 
 /* 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)
 {
-    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);
-            RegCloseKey(subkey);
+            close_regkey(subkey);
         }
-        RegDeleteKey(key, name);
+        del_regkey(key, name);
+        sfree(name);
     }
 }
 
 void cleanup_all(void)
 {
-    HKEY key;
-    int ret;
-    char name[MAX_PATH + 1];
-
     /* ------------------------------------------------------------
      * Wipe out the random seed file, in all of its possible
      * locations.
@@ -881,31 +857,34 @@ void cleanup_all(void)
     /*
      * 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);
-        RegCloseKey(key);
+        close_regkey(key);
     }
     /*
      * Now open the parent key and remove the PuTTY main key. Once
      * we've done that, see if the parent key has any other
      * 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
          * it in its turn. That means opening the _grandparent_
          * 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.
      */
 }
-

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

@@ -438,11 +438,116 @@ static const struct cp_list_item cp_list[] = {
     {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)
 {
-    int i, j;
+    int i;
     bool used_dtf = false;
     int vtmode;
 
@@ -525,31 +630,9 @@ void init_ucs(Conf *conf, struct unicode_data *ucsdata)
            sizeof(unitab_xterm_std));
     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) {
-        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. */
@@ -601,7 +684,7 @@ void init_ucs(Conf *conf, struct unicode_data *ucsdata)
         for (i = 96; i < 127; i++)
             if (!DIRECT_FONT(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++)
             if (!DIRECT_FONT(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;
     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,
         0x0437, 0x0439, 0x043a, 0x044b, 0x0435, 0x0433, 0x043c, 0x0446,
         0x0447, 0x043d, 0x044f, 0x0425, 0x0407, 0x042a,    126,    127
-       };
+    };
     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 {
@@ -1003,9 +1086,9 @@ int check_compose(int first, int second)
     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;
     int codepage = -1;
     CPINFO cpinfo;
@@ -1120,7 +1203,7 @@ const char *cp_enumerate(int index)
     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];
     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,
-             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. */
         if (wclen < 0) {
             for (wclen = 0; wcstr[wclen++] ;);   /* will include the NUL */
         }
+        char *p;
+        int i;
         for (p = mbstr, i = 0; i < wclen; i++) {
             wchar_t ch = wcstr[i];
             int by;
-            char *p1;
+            const char *p1;
 
             #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);             \
             } 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')
                 WRITECH(by);
             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,
              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);
     if (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;
 
- cleanup:
+  cleanup:
     return ret;
 }
 
@@ -186,7 +186,7 @@ bool make_private_security_descriptor(DWORD permissions,
     *error = NULL;
 
     if (!getsids(error))
-      goto cleanup;
+        goto cleanup;
 
     memset(ea, 0, sizeof(ea));
     ea[0].grfAccessPermissions = permissions;
@@ -301,10 +301,10 @@ static bool really_restrict_process_acl(char **error)
         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",
                            win_strerror(GetLastError()));
         goto cleanup;

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно