Browse Source

PuTTY development snapshot 2016-03-31 (7f3c956)

Source commit: 77575c0439a7744d3be94145141f1efc02ad300d
Martin Prikryl 9 years ago
parent
commit
164d2187cd

+ 89 - 26
source/putty/doc/config.but

@@ -1747,7 +1747,7 @@ arbitrary port (say, \cw{localhost} port 10022) were forwarded to a
 second machine's SSH port (say, \cw{foovax} port 22), and then
 started a second PuTTY connecting to the forwarded port.
 
-In normal usage, the second PuTTY will access the host key cache
+In normal usage, the second PuTTY will access the \i{host key cache}
 under the host name and port it actually connected to (i.e.
 \cw{localhost} port 10022 in this example). Using the logical host
 name option, however, you can configure the second PuTTY to cache
@@ -1762,7 +1762,7 @@ you to reconfirm its host key. Conversely, if you expect to use the
 same local port number for port forwardings to lots of different
 servers, you probably didn't want any particular server's host key
 cached under that local port number. (For this latter case, you
-could also explicitly configure host keys in the relevant sessions;
+could instead explicitly configure host keys in the relevant sessions;
 see \k{config-ssh-kex-manual-hostkeys}.)
 
 If you just enter a host name for this option, PuTTY will cache the
@@ -2483,6 +2483,53 @@ when the SSH connection is idle, so they shouldn't cause the same
 problems.  The SSH-1 protocol, incidentally, has even weaker integrity
 protection than SSH-2 without rekeys.
 
+\H{config-ssh-hostkey} The Host Keys panel
+
+The Host Keys panel allows you to configure options related to SSH-2
+\i{host key management}.
+
+Host keys are used to prove the server's identity, and assure you that
+the server is not being spoofed (either by a man-in-the-middle attack
+or by completely replacing it on the network). See \k{gs-hostkey} for
+a basic introduction to host keys.
+
+This entire panel is only relevant to SSH protocol version 2; none of
+these settings affect SSH-1 at all.
+
+\S{config-ssh-hostkey-order} \ii{Host key type} selection
+
+\cfg{winhelp-topic}{ssh.hostkey.order}
+
+PuTTY supports a variety of SSH-2 host key types, and allows you to
+choose which one you prefer to use to identify the server.
+Configuration is similar to cipher selection (see
+\k{config-ssh-encryption}).
+
+PuTTY currently supports the following host key types:
+
+\b \q{Ed25519}: \i{Edwards-curve} \i{DSA} using a twisted Edwards
+curve with modulus \cw{2^255-19}.
+
+\b \q{ECDSA}: \i{elliptic curve} \i{DSA} using one of the
+NIST-standardised elliptic curves.
+
+\b \q{DSA}: straightforward \i{DSA} using modular exponentiation.
+
+\b \q{RSA}: the ordinary \i{RSA} algorithm.
+
+If PuTTY already has one or more host keys stored for the server,
+it will prefer to use one of those, even if the server has a key
+type that is higher in the preference order. You can add such a
+key to PuTTY's cache from within an existing session using the
+\q{Special Commands} menu; see \k{using-specials}.
+
+Otherwise, PuTTY will choose a key type based purely on the
+preference order you specify in the configuration.
+
+If the first key type PuTTY finds is below the \q{warn below here}
+line, you will see a warning box when you make the connection, similar
+to that for cipher selection (see \k{config-ssh-encryption}).
+
 \S{config-ssh-kex-manual-hostkeys} \ii{Manually configuring host keys}
 
 \cfg{winhelp-topic}{ssh.kex.manualhostkeys}
@@ -2531,8 +2578,8 @@ If this box contains at least one host key or fingerprint when PuTTY
 makes an SSH connection, then PuTTY's automated host key management is
 completely bypassed: the connection will be permitted if and only if
 the host key presented by the server is one of the keys listed in this
-box, and the host key store in the Registry will be neither read
-\e{nor written}.
+box, and the \I{host key cache}host key store in the Registry will be
+neither read \e{nor written}, unless you explicitly do so.
 
 If the box is empty (as it usually is), then PuTTY's automated host
 key management will work as normal.
@@ -2596,22 +2643,6 @@ recommended ciphers.
 The Auth panel allows you to configure \i{authentication} options for
 SSH sessions.
 
-\S{config-ssh-noauth} \q{Bypass authentication entirely}
-
-\cfg{winhelp-topic}{ssh.auth.bypass}
-
-In SSH-2, it is possible to establish a connection without using SSH's
-mechanisms to identify or authenticate oneself to the server. Some
-servers may prefer to handle authentication in the data channel, for
-instance, or may simply require no authentication whatsoever.
-
-By default, PuTTY assumes the server requires authentication (most
-do), and thus must provide a username. If you find you are getting
-unwanted username prompts, you could try checking this option.
-
-This option only affects SSH-2 connections. SSH-1 connections always
-require an authentication step.
-
 \S{config-ssh-banner} \q{Display pre-authentication banner}
 
 \cfg{winhelp-topic}{ssh.auth.banner}
@@ -2627,6 +2658,34 @@ prompting for a login name, due to the nature of the protocol design).
 By unchecking this option, display of the banner can be suppressed
 entirely.
 
+\S{config-ssh-noauth} \q{Bypass authentication entirely}
+
+\cfg{winhelp-topic}{ssh.auth.bypass}
+
+In SSH-2, it is in principle possible to establish a connection
+without using SSH's mechanisms to identify or prove who you are
+to the server. An SSH server could prefer to handle authentication
+in the data channel, for instance, or simply require no user
+authentication whatsoever.
+
+By default, PuTTY assumes the server requires authentication (we've
+never heard of one that doesn't), and thus must start this process
+with a username. If you find you are getting username prompts that
+you cannot answer, you could try enabling this option. However,
+most SSH servers will reject this.
+
+This is not the option you want if you have a username and just want
+PuTTY to remember it; for that see \k{config-username}.
+It's also probably not what if you're trying to set up passwordless
+login to a mainstream SSH server; depending on the server, you
+probably wanted public-key authentication (\k{pubkey})
+or perhaps GSSAPI authentication (\k{config-ssh-auth-gssapi}).
+(These are still forms of authentication, even if you don't have to
+interact with them.)
+
+This option only affects SSH-2 connections. SSH-1 connections always
+require an authentication step.
+
 \S{config-ssh-tryagent} \q{Attempt authentication using Pageant}
 
 \cfg{winhelp-topic}{ssh.auth.pageant}
@@ -2729,11 +2788,15 @@ private key in another format that you want to use with PuTTY, see
 \k{puttygen-conversions}.
 
 You can use the authentication agent \i{Pageant} so that you do not
-need to explicitly configure a key here; see \k{pageant}. If a file
-is specified here with Pageant running, PuTTY will first try asking
-Pageant to authenticate with that key, and ignore any other keys
-Pageant may have. If that fails, PuTTY will ask for a passphrase as
-normal.
+need to explicitly configure a key here; see \k{pageant}.
+
+If a private key file is specified here with Pageant running, PuTTY
+will first try asking Pageant to authenticate with that key, and
+ignore any other keys Pageant may have. If that fails, PuTTY will ask
+for a passphrase as normal. You can also specify a \e{public} key file
+in this case (in RFC 4716 or OpenSSH format), as that's sufficient to
+identify the key to Pageant, but of course if Pageant isn't present
+PuTTY can't fall back to using this file itself.
 
 \H{config-ssh-auth-gssapi} The \i{GSSAPI} panel
 
@@ -2744,7 +2807,7 @@ GSSAPI authentication. This is a mechanism which delegates the
 authentication exchange to a library elsewhere on the client
 machine, which in principle can authenticate in many different ways
 but in practice is usually used with the \i{Kerberos} \i{single sign-on}
-protocol.
+protocol to implement \i{passwordless login}.
 
 GSSAPI is only available in the SSH-2 protocol.
 

+ 26 - 25
source/putty/doc/faq.but

@@ -222,14 +222,14 @@ processors that are backward-compatible with that architecture.
 (We used to also provide executables for Windows for the Alpha
 processor, but stopped after 0.58 due to lack of interest.)
 
-In the development code, partial ports to the Mac OSes exist (see
+In the development code, a partial port to Mac OS exists (see
 \k{faq-mac-port}).
 
 Currently PuTTY does \e{not} run on Windows CE (see \k{faq-wince}).
 
 We do not have release-quality ports for any other systems at the
-present time. If anyone told you we had an EPOC port, or an iPaq port,
-or any other port of PuTTY, they were mistaken. We don't.
+present time. If anyone told you we had an Android port, or an iOS
+port, or any other port of PuTTY, they were mistaken. We don't.
 
 There are some third-party ports to various platforms, mentioned
 on the 
@@ -246,16 +246,12 @@ including the usual \c{configure}/\c{make}; see the file \c{README}
 in the source distribution. This should build you Unix
 ports of Plink, PuTTY itself, PuTTYgen, PSCP, PSFTP, and also
 \i\c{pterm} - an \cw{xterm}-type program which supports the same
-terminal emulation as PuTTY. We do not yet have a Unix port of
-Pageant.
+terminal emulation as PuTTY. \#{XXX-REVIEW-BEFORE-RELEASE:}
+We do not yet have a Unix port of Pageant.
 
 If you don't have \i{Gtk}, you should still be able to build the
 command-line tools.
 
-Note that Unix PuTTY has mostly only been tested on Linux so far;
-portability problems such as BSD-style ptys or different header file
-requirements are expected.
-
 \S{faq-unix-why}{Question} What's the point of the Unix port? Unix
 has OpenSSH.
 
@@ -301,23 +297,24 @@ you'll need the right kind of C compiler - modern versions of Visual
 C at least have stopped being backwards compatible to Win32s. Also,
 the last time we tried this it didn't work very well.
 
-If you're interested in running PuTTY under Windows 3.1, help and
-testing in this area would be very welcome!
-
 \S{faq-mac-port}{Question} Will there be a port to the \I{Mac OS}Mac?
 
-There are several answers to this question:
+We hope so!
 
-\b The Unix/Gtk port is already fully working under Mac OS X as an X11
-application.
+We attempted one around 2005, written as a native Cocoa application,
+but it turned out to be very slow to redraw its window for some reason
+we never got to the bottom of.
 
-\b A native (Cocoa) Mac OS X port has been started. It's just about
-usable, but is of nowhere near release quality yet, and is likely to
-behave in unexpected ways. Currently it's unlikely to be completed
-unless someone steps in to help.
+In 2015, after porting the GTK front end to work with GTK 3, we began
+another attempt based on making small changes to the GTK code and
+building it against the OS X Quartz version of GTK 3. This doesn't
+seem to have the window redrawing problem any more, so it's already
+got further than the last effort, but it is still substantially
+unfinished.
 
-\b A separate port to the classic Mac OS (pre-OSX) is also in
-progress; it too is not ready yet.
+If any OS X and/or GTK programming experts are keen to have a finished
+version of this, we urge them to help out with some of the remaining
+problems!
 
 \S{faq-epoc}{Question} Will there be a port to EPOC?
 
@@ -334,8 +331,7 @@ for various third-party ports.
 
 We have no plans to write such a port ourselves; none of us has an
 iPhone, and developing and publishing applications for it looks
-awkward and expensive. Such a port would probably depend upon the
-stalled Mac OS X port (see \k{faq-mac-port}).
+awkward and expensive.
 
 However, there is a third-party SSH client for the iPhone and
 iPod\_Touch called \W{http://www.instantcocoa.com/products/pTerm/}{pTerm},
@@ -1106,8 +1102,13 @@ The PuTTY policy changed because the developers were informed of
 ways to implement DSA which do not suffer nearly as badly from this
 weakness, and indeed which don't need to rely on random numbers at
 all. For this reason we now believe PuTTY's DSA implementation is
-probably OK. However, if you have the choice, we still recommend you
-use RSA instead.
+probably OK.
+
+The recently added elliptic-curve signature methods are also DSA-style
+algorithms, so they have this same weakness in principle. Our ECDSA
+implementation uses the same defence as DSA, while our Ed25519
+implementation uses the similar system (but different in details) that
+the Ed25519 spec mandates.
 
 \S{faq-virtuallock}{Question} Couldn't Pageant use
 \cw{VirtualLock()} to stop private keys being written to disk?

+ 17 - 0
source/putty/doc/feedback.but

@@ -50,6 +50,9 @@ the URL; that way, we don't have to download it unless we decide we
 actually need it, and only one of us needs to download it instead of
 it being automatically copied to all the developers.
 
+(If the file contains confidential information, then you could encrypt
+it with our Secure Contact Key; see \k{pgpkeys-pubkey} for details.)
+
 Some people like to send mail in MS Word format. Please \e{don't}
 send us bug reports, or any other mail, as a Word document. Word
 documents are roughly fifty times larger than writing the same
@@ -201,6 +204,20 @@ will explain what you need to know. \e{Then}, if you think the
 documentation could usefully have told you that, send us a bug
 report and explain how you think we should change it.
 
+\H{feedback-vulns} Reporting security vulnerabilities
+
+If you've found a security vulnerability in PuTTY, you might well want
+to notify us using an encrypted communications channel, to avoid
+disclosing information about the vulnerability before a fixed release
+is available.
+
+For this purpose, we provide a GPG key suitable for encryption: the
+Secure Contact Key. See \k{pgpkeys-pubkey} for details of this.
+
+(Of course, vulnerabilities are also bugs, so please do include as
+much information as possible about them, the same way you would with
+any other bug report.)
+
 \H{feedback-features} Requesting extra features 
 
 If you want to request a new feature in PuTTY, the very first things

+ 10 - 8
source/putty/doc/gs.but

@@ -77,13 +77,13 @@ server and it sends you a different host key from the one you were
 expecting, PuTTY can warn you that the server may have been switched
 and that a spoofing attack might be in progress.
 
-PuTTY records the host key for each server you connect to, in the
-Windows \i{Registry}. Every time you connect to a server, it checks
-that the host key presented by the server is the same host key as it
-was the last time you connected. If it is not, you will see a
-warning, and you will have the chance to abandon your connection
-before you type any private information (such as a password) into
-it.
+PuTTY \I{host key cache}records the host key for each server you
+connect to, in the Windows \i{Registry}. Every time you connect to a
+server, it checks that the host key presented by the server is the
+same host key as it was the last time you connected. If it is not,
+you will see a warning, and you will have the chance to abandon your
+connection before you type any private information (such as a
+password) into it.
 
 However, when you connect to a server you have not connected to
 before, PuTTY has no way of telling whether the host key is the
@@ -97,11 +97,13 @@ network users are on the same side and spoofing attacks are
 unlikely, so you might choose to trust the key without checking it.
 If you are connecting across a hostile network (such as the
 Internet), you should check with your system administrator, perhaps
-by telephone or in person. (Some modern servers have more than one
+by telephone or in person. (Many servers have more than one
 host key. If the system administrator sends you more than one
 \I{host key fingerprint}fingerprint, you should make sure the one
 PuTTY shows you is on the list, but it doesn't matter which one it is.)
 
+See \k{config-ssh-hostkey} for advanced options for managing host keys.
+
 \# FIXME: this is all very fine but of course in practice the world
 doesn't work that way. Ask the team if they have any good ideas for
 changes to this section!

+ 3 - 1
source/putty/doc/index.but

@@ -850,7 +850,9 @@ saved sessions from
 
 \IM{logical host name} logical host name
 \IM{logical host name} host name, logical
-\IM{logical host name} host key, caching policy
+
+\IM{host key cache}{host key management} host key management
+\IM{host key cache}{host key management} cache, of SSH host keys
 
 \IM{web browsers} web browser
 

+ 17 - 5
source/putty/doc/man-pag.but

@@ -4,7 +4,7 @@
 
 \S{pageant-manpage-name} NAME
 
-\cw{pageant} - SSH authentication agent for the PuTTY tools
+\cw{pageant} - PuTTY SSH authentication agent
 
 \S{pageant-manpage-synopsis} SYNOPSIS
 
@@ -37,6 +37,10 @@ that the server they are connecting to will accept.
 with an already-running agent to add or remove keys, list the keys, or
 extract their public half.
 
+The agent protocol used by \c{pageant} is compatible with the PuTTY
+tools and also with other implementations such as OpenSSH's SSH client
+and \e{ssh-agent(1)}.
+
 To run \c{pageant} as an agent, you must provide an option to tell it
 what its \e{lifetime} should be. Typically you would probably want
 Pageant to last for the duration of a login session, in which case you
@@ -71,9 +75,9 @@ extra command-line arguments, e.g.
 
 in which case Pageant will prompt for the keys' passphrases (if any)
 and start the agent with those keys already loaded. Passphrase prompts
-will use the controlling terminal if one is available, or the GUI if
-one of those is available. If neither is available, no passphrase
-prompting can be done.
+will use the controlling terminal if one is available, or failing that
+the GUI if one of those is available. If neither is available, no
+passphrase prompting can be done.
 
 To use Pageant to talk to an existing agent, you can add new keys
 using \cw{-a}, list the current set of keys' fingerprints and comments
@@ -155,7 +159,7 @@ before it manages to happen.
 \dt \cw{--debug}
 
 \dd Pageant will run in the foreground, without forking. It will print
-its enviroment variable setup commands on standard output, and then it
+its environment variable setup commands on standard output, and then it
 will log all agent activity to standard output as well. This is useful
 for debugging what Pageant itself is doing, or what another process is
 doing to it.
@@ -256,6 +260,14 @@ output.
 
 }
 
+\dt \cw{-s}, \cw{-c}
+
+\dd Force Pageant to output its environment setup commands in the
+style of POSIX / Bourne shells (\cw{-s}) or C shells (\cw{-c})
+respectively. If neither option is given, Pageant will guess based on
+whether the environment variable \cw{SHELL} has a value ending in
+\cq{csh}.
+
 \dt \cw{--help}
 
 \dd Print a brief summary of command-line options and terminate.

+ 15 - 5
source/putty/doc/man-pg.but

@@ -64,6 +64,13 @@ and \c{rsa1} (to generate SSH-1 keys).
 
 \dd Suppress the progress display when generating a new key.
 
+\dt \cw{\-\-old\-passphrase} \e{file}
+
+\dd Specify a file name; the first line will be read from this file
+(removing any trailing newline) and used as the old passphrase.
+\s{CAUTION:} If the passphrase is important, the file should be stored
+on a temporary filesystem or else securely erased after use.
+
 In the second phase, \c{puttygen} optionally alters properties of
 the key it has loaded or generated. The options to control this are:
 
@@ -156,6 +163,14 @@ fingerprint. Otherwise, the \c{\-o} option is required.
 
 \dd Synonym for \q{\cw{-O public}}.
 
+\dt \cw{\-\-new\-passphrase} \e{file}
+
+\dd Specify a file name; the first line will be read from this file
+(removing any trailing newline) and used as the new passphrase. If the
+file is empty then the saved key will be unencrypted. \s{CAUTION:} If
+the passphrase is important, the file should be stored on a temporary
+filesystem or else securely erased after use.
+
 The following options do not run PuTTYgen as normal, but print
 informational messages and then quit:
 
@@ -210,8 +225,3 @@ To add the OpenSSH-format public half of a key to your authorised
 keys file:
 
 \c puttygen -L mykey.ppk >> $HOME/.ssh/authorized_keys
-
-\S{puttygen-manpage-bugs} BUGS
-
-There's currently no way to supply passphrases in batch mode, or
-even just to specify that you don't want a passphrase at all.

+ 8 - 2
source/putty/doc/man-pl.but

@@ -137,9 +137,15 @@ tunnel all their connections. Only works in SSH.
 
 \dd Enable SSH compression.
 
-\dt \cw{-i} \e{path}
+\dt \cw{-i} \e{keyfile}
 
-\dd Private key file for user authentication.
+\dd Private key file for user authentication. For SSH-2 keys, this key
+file must be in PuTTY's PPK format, not OpenSSH's format or anyone
+else's.
+
+\lcont{ If you are using an authentication agent, you can also specify
+a \e{public} key here (in RFC 4716 or OpenSSH format), to identify
+which of the agent's keys to use. }
 
 \dt \cw{\-hostkey} \e{key}
 

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

@@ -91,9 +91,15 @@ commands such as \q{\c{w}}).
 
 \dd Enable SSH compression.
 
-\dt \cw{-i} \e{path}
+\dt \cw{-i} \e{keyfile}
 
-\dd Private key file for user authentication.
+\dd Private key file for user authentication. For SSH-2 keys, this key
+file must be in PuTTY's PPK format, not OpenSSH's format or anyone
+else's.
+
+\lcont{ If you are using an authentication agent, you can also specify
+a \e{public} key here (in RFC 4716 or OpenSSH format), to identify
+which of the agent's keys to use. }
 
 \dt \cw{\-hostkey} \e{key}
 

+ 8 - 2
source/putty/doc/man-psft.but

@@ -79,9 +79,15 @@ commands such as \q{\c{w}}).
 
 \dd Enable SSH compression.
 
-\dt \cw{-i} \e{path}
+\dt \cw{-i} \e{keyfile}
 
-\dd Private key file for user authentication.
+\dd Private key file for user authentication. For SSH-2 keys, this key
+file must be in PuTTY's PPK format, not OpenSSH's format or anyone
+else's.
+
+\lcont{ If you are using an authentication agent, you can also specify
+a \e{public} key here (in RFC 4716 or OpenSSH format), to identify
+which of the agent's keys to use. }
 
 \dt \cw{\-hostkey} \e{key}
 

+ 1 - 0
source/putty/doc/man-ptel.but

@@ -31,6 +31,7 @@ Sorry.)
 \dt \cw{\-fn} \e{font-name}
 
 \dd Specify the font to use for normal text displayed in the terminal.
+For example, \cw{\-fn\_fixed}, \cw{\-fn\_"Monospace\_12"}.
 
 \dt \cw{\-fb} \e{font-name}
 

+ 1 - 0
source/putty/doc/man-pter.but

@@ -51,6 +51,7 @@ sets of defaults and choose between them.
 \dt \cw{\-fn} \e{font-name}
 
 \dd Specify the font to use for normal text displayed in the terminal.
+For example, \cw{\-fn\_fixed}, \cw{\-fn\_"Monospace\_12"}.
 
 \dt \cw{\-fb} \e{font-name}
 

+ 8 - 3
source/putty/doc/man-putt.but

@@ -30,6 +30,7 @@ Sorry.)
 \dt \cw{\-fn} \e{font-name}
 
 \dd Specify the font to use for normal text displayed in the terminal.
+For example, \cw{\-fn\_fixed}, \cw{\-fn\_"Monospace\_12"}.
 
 \dt \cw{\-fb} \e{font-name}
 
@@ -232,9 +233,13 @@ pseudo-terminal at the server end.
 
 \dt \cw{\-i} \e{keyfile}
 
-\dd Specify a private key file to use for user authentication. For SSH-2
-keys, this key file must be in PuTTY's format, not OpenSSH's or
-anyone else's.
+\dd Private key file for user authentication. For SSH-2 keys, this key
+file must be in PuTTY's PPK format, not OpenSSH's format or anyone
+else's.
+
+\lcont{ If you are using an authentication agent, you can also specify
+a \e{public} key here (in RFC 4716 or OpenSSH format), to identify
+which of the agent's keys to use. }
 
 \dt \cw{\-hostkey} \e{key}
 

+ 6 - 6
source/putty/doc/pageant.but

@@ -64,8 +64,8 @@ The large list box in the Pageant main window lists the private keys
 that are currently loaded into Pageant. The list might look
 something like this:
 
-\c ssh1    1024 22:c3:68:3b:09:41:36:c3:39:83:91:ae:71:b2:0f:04 k1
-\c ssh-rsa 1023 74:63:08:82:95:75:e1:7c:33:31:bb:cb:00:c0:89:8b k2
+\c ssh-rsa 2048 22:d6:69:c9:22:51:ac:cb:b9:15:67:47:f7:65:6d:d7 k1
+\c ssh-dss 2048 e4:6c:69:f3:4f:fc:cf:fc:96:c0:88:34:a7:1e:59:d7 k2
 
 For each key, the list box will tell you:
 
@@ -260,10 +260,10 @@ as long as they want.
 However, the sysadmin of the server machine can always pretend to be
 you \e{on that machine}. So if you forward your agent to a server
 machine, then the sysadmin of that machine can access the forwarded
-agent connection and request signatures from your private keys, and
-can therefore log in to other machines as you. They can only do this
-to a limited extent - when the agent forwarding disappears they lose
-the ability - but using Pageant doesn't actually \e{prevent} the
+agent connection and request signatures from any of your private keys,
+and can therefore log in to other machines as you. They can only do
+this to a limited extent - when the agent forwarding disappears they
+lose the ability - but using Pageant doesn't actually \e{prevent} the
 sysadmin (or hackers) on the server from doing this.
 
 Therefore, if you don't trust the sysadmin of a server machine, you

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

@@ -69,7 +69,7 @@ IDs listed below.
 
 \dd RSA, 2048-bit. Main key ID: \cw{2048R/8A0AF00B} (long version:
 \cw{2048R/C4FCAAD08A0AF00B}). Encryption subkey ID:
-\cw{2048R/50C2CF5C} (long version: \cw{2048R/9EB39CC150C2CF5C}.
+\cw{2048R/50C2CF5C} (long version: \cw{2048R/9EB39CC150C2CF5C}).
 Fingerprint:
 \cw{8A26\_250E\_763F\_E359\_75F3\_\_118F\_C4FC\_AAD0\_8A0A\_F00B}
 

+ 1 - 17
source/putty/doc/pubkey.but

@@ -131,22 +131,6 @@ key will be completely useless.
 The SSH-2 protocol supports more than one key type. The types
 supported by PuTTY are RSA, DSA, ECDSA, and Ed25519.
 
-The PuTTY developers \e{strongly} recommend you use RSA.
-\#{FIXME: ECDSA, Ed25519!}
-\I{security risk}\i{DSA} has an intrinsic weakness which makes it very
-easy to create a signature which contains enough information to give
-away the \e{private} key!
-This would allow an attacker to pretend to be you for any number of
-future sessions. PuTTY's implementation has taken very careful
-precautions to avoid this weakness, but we cannot be 100% certain we
-have managed it, and if you have the choice we strongly recommend
-using RSA keys instead.
-
-If you really need to connect to an SSH server which only supports
-DSA, then you probably have no choice but to use DSA. If you do use
-DSA, we recommend you do not use the same key to authenticate with
-more than one server.
-
 \S{puttygen-strength} Selecting the size (strength) of the key
 
 \cfg{winhelp-topic}{puttygen.bits}
@@ -391,7 +375,7 @@ OpenSSH; for newer key types like Ed25519, it will use the newer
 format as that is the only legal option. If you have some specific
 reason for wanting to use OpenSSH's newer format even for RSA, DSA,
 or ECDSA keys, you can choose \q{Export OpenSSH key (force new file
-format}.
+format)}.
 
 Note that since only SSH-2 keys come in different formats, the export
 options are not available if you have generated an SSH-1 key.

+ 45 - 14
source/putty/doc/using.but

@@ -121,6 +121,9 @@ and hit the Copy button to copy them to the \i{clipboard}. If you
 are reporting a bug, it's often useful to paste the contents of the
 Event Log into your bug report.
 
+(The Event Log is not the same as the facility to create a log file
+of your session; that's described in \k{using-logging}.)
+
 \S2{using-specials} \ii{Special commands}
 
 Depending on the protocol used for the current session, there may be
@@ -198,6 +201,29 @@ resets associated timers and counters). For more information about
 repeat key exchanges, see \k{config-ssh-kex-rekey}.
 }
 
+\b \I{host key cache}Cache new host key type
+
+\lcont{
+Only available in SSH-2. This submenu appears only if the server has
+host keys of a type that PuTTY doesn't already have cached, and so
+won't consider. Selecting a key here will allow PuTTY to use that key
+now and in future: PuTTY will do a fresh key-exchange with the selected
+key, and immediately add that key to its permanent cache (relying on
+the host key used at the start of the connection to cross-certify the
+new key). That key will be used for the rest of the current session;
+it may not actually be used for future sessions, depending on your
+preferences (see \k{config-ssh-hostkey-order}).
+
+Normally, PuTTY will carry on using a host key it already knows, even
+if the server offers key formats that PuTTY would otherwise prefer,
+to avoid host key prompts. As a result, if you've been using a server
+for some years, you may still be using an older key than a new user
+would use, due to server upgrades in the meantime. The SSH protocol
+unfortunately does not have organised facilities for host key migration
+and rollover, but this allows you to \I{host keys, upgrading}manually
+upgrade.
+}
+
 \b \I{Break, SSH special command}Break
 
 \lcont{
@@ -316,8 +342,9 @@ If you find that special characters (\i{accented characters}, for
 example, or \i{line-drawing characters}) are not being displayed
 correctly in your PuTTY session, it may be that PuTTY is interpreting
 the characters sent by the server according to the wrong \e{character
-set}. There are a lot of different character sets available, so it's
-entirely possible for this to happen.
+set}. There are a lot of different character sets available, and no
+good way for PuTTY to know which to use, so it's entirely possible
+for this to happen.
 
 If you click \q{Change Settings} and look at the \q{Translation}
 panel, you should see a large number of character sets which you can
@@ -894,6 +921,10 @@ The \c{-i} option allows you to specify the name of a private key
 file in \c{*.\i{PPK}} format which PuTTY will use to authenticate with the
 server. This option is only meaningful if you are using SSH.
 
+If you are using Pageant, you can also specify a \e{public} key file
+(in RFC 4716 or OpenSSH format) to identify a specific key file to use.
+(This won't work if you're not running Pageant, of course.)
+
 For general information on \i{public-key authentication}, see
 \k{pubkey}.
 
@@ -904,22 +935,22 @@ authentication} box in the Auth panel of the PuTTY configuration box
 \S2{using-cmdline-loghost} \i\c{-loghost}: specify a \i{logical host
 name}
 
-This option overrides PuTTY's normal SSH host key caching policy by
-telling it the name of the host you expect your connection to end up
-at (in cases where this differs from the location PuTTY thinks it's
-connecting to). It can be a plain host name, or a host name followed
-by a colon and a port number. See \k{config-loghost} for more detail
-on this.
+This option overrides PuTTY's normal SSH \I{host key cache}host key
+caching policy by telling it the name of the host you expect your
+connection to end up at (in cases where this differs from the location
+PuTTY thinks it's connecting to). It can be a plain host name, or a
+host name followed by a colon and a port number. See
+\k{config-loghost} for more detail on this.
 
 \S2{using-cmdline-hostkey} \i\c{-hostkey}: \I{manually configuring
 host keys}manually specify an expected host key
 
-This option overrides PuTTY's normal SSH host key caching policy by
-telling it exactly what host key to expect, which can be useful if the
-normal automatic host key store in the Registry is unavailable. The
-argument to this option should be either a host key fingerprint, or an
-SSH-2 public key blob. See \k{config-ssh-kex-manual-hostkeys} for more
-information.
+This option overrides PuTTY's normal SSH \I{host key cache}host key
+caching policy by telling it exactly what host key to expect, which
+can be useful if the normal automatic host key store in the Registry
+is unavailable. The argument to this option should be either a host key
+fingerprint, or an SSH-2 public key blob. See
+\k{config-ssh-kex-manual-hostkeys} for more information.
 
 You can specify this option more than once if you want to configure
 more than one key to be accepted.

+ 31 - 4
source/putty/putty.h

@@ -182,7 +182,14 @@ typedef enum {
     /* Pseudo-specials used for constructing the specials menu. */
     TS_SEP,	    /* Separator */
     TS_SUBMENU,	    /* Start a new submenu with specified name */
-    TS_EXITMENU	    /* Exit current submenu or end of specials */
+    TS_EXITMENU,    /* Exit current submenu or end of specials */
+    /* Starting point for protocols to invent special-action codes
+     * that can't live in this enum at all, e.g. because they change
+     * with every session.
+     *
+     * Of course, this must remain the last value in this
+     * enumeration. */
+    TS_LOCALSTART
 } Telnet_Special;
 
 struct telnet_special {
@@ -259,6 +266,18 @@ enum {
     KEX_MAX
 };
 
+enum {
+    /*
+     * SSH-2 host key algorithms
+     */
+    HK_WARN,
+    HK_RSA,
+    HK_DSA,
+    HK_ECDSA,
+    HK_ED25519,
+    HK_MAX
+};
+
 enum {
     /*
      * SSH ciphers (both SSH-1 and SSH-2)
@@ -688,6 +707,7 @@ void cleanup_exit(int);
     X(INT, NONE, nopty) \
     X(INT, NONE, compression) \
     X(INT, INT, ssh_kexlist) \
+    X(INT, INT, ssh_hklist) \
     X(INT, NONE, ssh_rekey_time) /* in minutes */ \
     X(STR, NONE, ssh_rekey_data) /* string encoding e.g. "100K", "2M", "1G" */ \
     X(INT, NONE, tryagent) \
@@ -1208,14 +1228,21 @@ int verify_ssh_host_key(void *frontend, char *host, int port,
                         void (*callback)(void *ctx, int result), void *ctx);
 /*
  * have_ssh_host_key() just returns true if a key of that type is
- * already chached and false otherwise.
+ * already cached and false otherwise.
  */
 int have_ssh_host_key(const char *host, int port, const char *keytype);
 /*
- * askalg has the same set of return values as verify_ssh_host_key.
+ * askalg and askhk have the same set of return values as
+ * verify_ssh_host_key.
+ *
+ * (askhk is used in the case where we're using a host key below the
+ * warning threshold because that's all we have cached, but at least
+ * one acceptable algorithm is available that we don't have cached.)
  */
 int askalg(void *frontend, const char *algtype, const char *algname,
 	   void (*callback)(void *ctx, int result), void *ctx);
+int askhk(void *frontend, const char *algname, const char *betteralgs,
+          void (*callback)(void *ctx, int result), void *ctx);
 /*
  * askappend can return four values:
  * 
@@ -1325,7 +1352,7 @@ void filename_free(Filename *fn);
 int filename_serialise(const Filename *f, void *data);
 Filename *filename_deserialise(void *data, int maxsize, int *used);
 char *get_username(void);	       /* return value needs freeing */
-char *get_random_data(int bytes);      /* used in cmdgen.c */
+char *get_random_data(int bytes, const char *device); /* used in cmdgen.c */
 char filename_char_sanitise(char c);   /* rewrite special pathname chars */
 
 /*

+ 0 - 4
source/putty/puttyps.h

@@ -9,10 +9,6 @@
 
 #include "winstuff.h"
 
-#elif defined(MACOSX)
-
-#include "osx.h"
-
 #else
 
 #include "unix.h"

+ 299 - 40
source/putty/ssh.c

@@ -11,6 +11,7 @@
 
 #include "putty.h"
 #include "tree234.h"
+#include "storage.h"
 #include "ssh.h"
 #ifndef NO_GSSAPI
 #include "sshgssc.h"
@@ -407,16 +408,23 @@ static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin);
 #define OUR_V2_MAXPKT 0x4000UL
 #define OUR_V2_PACKETLIMIT 0x9000UL
 
-const static struct ssh_signkey *hostkey_algs[] = {
-    &ssh_ecdsa_ed25519,
-    &ssh_ecdsa_nistp256, &ssh_ecdsa_nistp384, &ssh_ecdsa_nistp521,
-    &ssh_rsa, &ssh_dss
+struct ssh_signkey_with_user_pref_id {
+    const struct ssh_signkey *alg;
+    int id;
+};
+const static struct ssh_signkey_with_user_pref_id hostkey_algs[] = {
+    { &ssh_ecdsa_ed25519, HK_ED25519 },
+    { &ssh_ecdsa_nistp256, HK_ECDSA },
+    { &ssh_ecdsa_nistp384, HK_ECDSA },
+    { &ssh_ecdsa_nistp521, HK_ECDSA },
+    { &ssh_dss, HK_DSA },
+    { &ssh_rsa, HK_RSA },
 };
 
-const static struct ssh_mac *macs[] = {
+const static struct ssh_mac *const macs[] = {
     &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5
 };
-const static struct ssh_mac *buggymacs[] = {
+const static struct ssh_mac *const buggymacs[] = {
     &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5
 };
 
@@ -443,7 +451,7 @@ const static struct ssh_compress ssh_comp_none = {
     ssh_comp_none_disable, NULL
 };
 extern const struct ssh_compress ssh_zlib;
-const static struct ssh_compress *compressions[] = {
+const static struct ssh_compress *const compressions[] = {
     &ssh_zlib, &ssh_comp_none
 };
 
@@ -954,6 +962,24 @@ struct ssh_tag {
      */
     struct ssh_gss_liblist *gsslibs;
 #endif
+
+    /*
+     * The last list returned from get_specials.
+     */
+    struct telnet_special *specials;
+
+    /*
+     * List of host key algorithms for which we _don't_ have a stored
+     * host key. These are indices into the main hostkey_algs[] array
+     */
+    int uncert_hostkeys[lenof(hostkey_algs)];
+    int n_uncert_hostkeys;
+
+    /*
+     * Flag indicating that the current rekey is intended to finish
+     * with a newly cross-certified host key.
+     */
+    int cross_certifying;
 };
 
 #define logevent(s) logevent(ssh->frontend, s)
@@ -6223,7 +6249,10 @@ struct kexinit_algorithm {
 	    const struct ssh_kex *kex;
 	    int warn;
 	} kex;
-	const struct ssh_signkey *hostkey;
+	struct {
+            const struct ssh_signkey *hostkey;
+            int warn;
+        } hk;
 	struct {
 	    const struct ssh2_cipher *cipher;
 	    int warn;
@@ -6278,12 +6307,12 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 	"server-to-client compression method" };
     struct do_ssh2_transport_state {
 	int crLine;
-	int nbits, pbits, warn_kex, warn_cscipher, warn_sccipher;
+	int nbits, pbits, warn_kex, warn_hk, warn_cscipher, warn_sccipher;
 	Bignum p, g, e, f, K;
 	void *our_kexinit;
 	int our_kexinitlen;
 	int kex_init_value, kex_reply_value;
-	const struct ssh_mac **maclist;
+	const struct ssh_mac *const *maclist;
 	int nmacs;
 	const struct ssh2_cipher *cscipher_tobe;
 	const struct ssh2_cipher *sccipher_tobe;
@@ -6300,6 +6329,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 	unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN];
 	int n_preferred_kex;
 	const struct ssh_kexes *preferred_kex[KEX_MAX];
+	int n_preferred_hk;
+	int preferred_hk[HK_MAX];
 	int n_preferred_ciphers;
 	const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
 	const struct ssh_compress *preferred_comp;
@@ -6376,6 +6407,20 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 	    }
 	}
 
+	/*
+	 * Set up the preferred host key types. These are just the ids
+	 * in the enum in putty.h, so 'warn below here' is indicated
+	 * by HK_WARN.
+	 */
+	s->n_preferred_hk = 0;
+	for (i = 0; i < HK_MAX; i++) {
+            int id = conf_get_int_int(ssh->conf, CONF_ssh_hklist, i);
+            /* As above, don't bother with HK_WARN if it's last in the
+             * list */
+	    if (id != HK_WARN || i < HK_MAX - 1)
+                s->preferred_hk[s->n_preferred_hk++] = id;
+	}
+
 	/*
 	 * Set up the preferred ciphers. (NULL => warn below here)
 	 */
@@ -6452,20 +6497,43 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
              * In the first key exchange, we list all the algorithms
              * we're prepared to cope with, but prefer those algorithms
 	     * for which we have a host key for this host.
+             *
+             * If the host key algorithm is below the warning
+             * threshold, we warn even if we did already have a key
+             * for it, on the basis that if the user has just
+             * reconfigured that host key type to be warned about,
+             * they surely _do_ want to be alerted that a server
+             * they're actually connecting to is using it.
              */
-            for (i = 0; i < lenof(hostkey_algs); i++) {
-		if (have_ssh_host_key(ssh->savedhost, ssh->savedport,
-				      hostkey_algs[i]->keytype)) {
-		    alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
-					      hostkey_algs[i]->name);
-		    alg->u.hostkey = hostkey_algs[i];
-		}
-	    }
-            for (i = 0; i < lenof(hostkey_algs); i++) {
-		alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
-					  hostkey_algs[i]->name);
-		alg->u.hostkey = hostkey_algs[i];
+            warn = FALSE;
+            for (i = 0; i < s->n_preferred_hk; i++) {
+                if (s->preferred_hk[i] == HK_WARN)
+                    warn = TRUE;
+                for (j = 0; j < lenof(hostkey_algs); j++) {
+                    if (hostkey_algs[j].id != s->preferred_hk[i])
+                        continue;
+                    if (have_ssh_host_key(ssh->savedhost, ssh->savedport,
+                                          hostkey_algs[j].alg->keytype)) {
+                        alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
+                                                  hostkey_algs[j].alg->name);
+                        alg->u.hk.hostkey = hostkey_algs[j].alg;
+                        alg->u.hk.warn = warn;
+                    }
+                }
 	    }
+            warn = FALSE;
+            for (i = 0; i < s->n_preferred_hk; i++) {
+                if (s->preferred_hk[i] == HK_WARN)
+                    warn = TRUE;
+                for (j = 0; j < lenof(hostkey_algs); j++) {
+                    if (hostkey_algs[j].id != s->preferred_hk[i])
+                        continue;
+                    alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
+                                              hostkey_algs[j].alg->name);
+                    alg->u.hk.hostkey = hostkey_algs[j].alg;
+                    alg->u.hk.warn = warn;
+                }
+            }
         } else {
             /*
              * In subsequent key exchanges, we list only the kex
@@ -6477,7 +6545,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
             assert(ssh->kex);
 	    alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
 				      ssh->hostkey->name);
-	    alg->u.hostkey = ssh->hostkey;
+	    alg->u.hk.hostkey = ssh->hostkey;
+            alg->u.hk.warn = FALSE;
         }
 	/* List encryption algorithms (client->server then server->client). */
 	for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) {
@@ -6598,7 +6667,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 	s->scmac_tobe = NULL;
 	s->cscomp_tobe = NULL;
 	s->sccomp_tobe = NULL;
-	s->warn_kex = s->warn_cscipher = s->warn_sccipher = FALSE;
+	s->warn_kex = s->warn_hk = FALSE;
+        s->warn_cscipher = s->warn_sccipher = FALSE;
 
 	pktin->savedpos += 16;	        /* skip garbage cookie */
 
@@ -6642,7 +6712,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 			ssh->kex = alg->u.kex.kex;
 			s->warn_kex = alg->u.kex.warn;
 		    } else if (i == KEXLIST_HOSTKEY) {
-			ssh->hostkey = alg->u.hostkey;
+			ssh->hostkey = alg->u.hk.hostkey;
+                        s->warn_hk = alg->u.hk.warn;
 		    } else if (i == KEXLIST_CSCIPHER) {
 			s->cscipher_tobe = alg->u.cipher.cipher;
 			s->warn_cscipher = alg->u.cipher.warn;
@@ -6666,10 +6737,37 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 		    in_commasep_string(alg->u.comp->delayed_name, str, len))
 		    s->pending_compression = TRUE;  /* try this later */
 	    }
-	    bombout(("Couldn't agree a %s ((available: %.*s)",
+	    bombout(("Couldn't agree a %s (available: %.*s)",
 		     kexlist_descr[i], len, str));
 	    crStopV;
 	  matched:;
+
+            if (i == KEXLIST_HOSTKEY) {
+                int j;
+
+                /*
+                 * In addition to deciding which host key we're
+                 * actually going to use, we should make a list of the
+                 * host keys offered by the server which we _don't_
+                 * have cached. These will be offered as cross-
+                 * certification options by ssh_get_specials.
+                 *
+                 * We also count the key we're currently using for KEX
+                 * as one we've already got, because by the time this
+                 * menu becomes visible, it will be.
+                 */
+                ssh->n_uncert_hostkeys = 0;
+
+                for (j = 0; j < lenof(hostkey_algs); j++) {
+                    if (hostkey_algs[j].alg != ssh->hostkey &&
+                        in_commasep_string(hostkey_algs[j].alg->name,
+                                           str, len) &&
+                        !have_ssh_host_key(ssh->savedhost, ssh->savedport,
+                                           hostkey_algs[j].alg->keytype)) {
+                        ssh->uncert_hostkeys[ssh->n_uncert_hostkeys++] = j;
+                    }
+                }
+            }
 	}
 
 	if (s->pending_compression) {
@@ -6714,6 +6812,73 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 	    }
 	}
 
+	if (s->warn_hk) {
+            int j, k;
+            char *betteralgs;
+
+	    ssh_set_frozen(ssh, 1);
+
+            /*
+             * Change warning box wording depending on why we chose a
+             * warning-level host key algorithm. If it's because
+             * that's all we have *cached*, use the askhk mechanism,
+             * and list the host keys we could usefully cross-certify.
+             * Otherwise, use askalg for the standard wording.
+             */
+            betteralgs = NULL;
+            for (j = 0; j < ssh->n_uncert_hostkeys; j++) {
+                const struct ssh_signkey_with_user_pref_id *hktype =
+                    &hostkey_algs[ssh->uncert_hostkeys[j]];
+                int better = FALSE;
+                for (k = 0; k < HK_MAX; k++) {
+                    int id = conf_get_int_int(ssh->conf, CONF_ssh_hklist, k);
+                    if (id == HK_WARN) {
+                        break;
+                    } else if (id == hktype->id) {
+                        better = TRUE;
+                        break;
+                    }
+                }
+                if (better) {
+                    if (betteralgs) {
+                        char *old_ba = betteralgs;
+                        betteralgs = dupcat(betteralgs, ",",
+                                            hktype->alg->name,
+                                            (const char *)NULL);
+                        sfree(old_ba);
+                    } else {
+                        betteralgs = dupstr(hktype->alg->name);
+                    }
+                }
+            }
+            if (betteralgs) {
+                s->dlgret = askhk(ssh->frontend, ssh->hostkey->name,
+                                  betteralgs, ssh_dialog_callback, ssh);
+                sfree(betteralgs);
+            } else {
+                s->dlgret = askalg(ssh->frontend, "host key type",
+                                   ssh->hostkey->name,
+                                   ssh_dialog_callback, ssh);
+            }
+	    if (s->dlgret < 0) {
+		do {
+		    crReturnV;
+		    if (pktin) {
+			bombout(("Unexpected data from server while"
+				 " waiting for user response"));
+			crStopV;
+		    }
+		} while (pktin || inlen > 0);
+		s->dlgret = ssh->user_response;
+	    }
+	    ssh_set_frozen(ssh, 0);
+	    if (s->dlgret == 0) {
+		ssh_disconnect(ssh, "User aborted at host key warning", NULL,
+			       0, TRUE);
+		crStopV;
+	    }
+	}
+
 	if (s->warn_cscipher) {
 	    ssh_set_frozen(ssh, 1);
 	    s->dlgret = askalg(ssh->frontend,
@@ -7130,6 +7295,40 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 
     s->keystr = ssh->hostkey->fmtkey(s->hkey);
     if (!s->got_session_id) {
+	/*
+	 * Make a note of any other host key formats that are available.
+	 */
+	{
+	    int i, j;
+	    char *list = NULL;
+	    for (i = 0; i < lenof(hostkey_algs); i++) {
+		if (hostkey_algs[i].alg == ssh->hostkey)
+		    continue;
+
+                for (j = 0; j < ssh->n_uncert_hostkeys; j++)
+                    if (ssh->uncert_hostkeys[j] == i)
+                        break;
+
+                if (j < ssh->n_uncert_hostkeys) {
+		    char *newlist;
+		    if (list)
+			newlist = dupprintf("%s/%s", list,
+					    hostkey_algs[i].alg->name);
+		    else
+			newlist = dupprintf("%s", hostkey_algs[i].alg->name);
+		    sfree(list);
+		    list = newlist;
+		}
+	    }
+	    if (list) {
+		logeventf(ssh,
+			  "Server also has %s host key%s, but we "
+			  "don't know %s", list,
+			  j > 1 ? "s" : "", j > 1 ? "any of them" : "it");
+		sfree(list);
+	    }
+	}
+
         /*
          * Authenticate remote host: verify host key. (We've already
          * checked the signature of the exchange hash.)
@@ -7177,6 +7376,18 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
          * subsequent rekeys.
          */
         ssh->hostkey_str = s->keystr;
+    } else if (ssh->cross_certifying) {
+        s->fingerprint = ssh2_fingerprint(ssh->hostkey, s->hkey);
+        logevent("Storing additional host key for this host:");
+        logevent(s->fingerprint);
+        store_host_key(ssh->savedhost, ssh->savedport,
+                       ssh->hostkey->keytype, s->keystr);
+        ssh->cross_certifying = FALSE;
+        /*
+         * Don't forget to store the new key as the one we'll be
+         * re-checking in future normal rekeys.
+         */
+        ssh->hostkey_str = s->keystr;
     } else {
         /*
          * In a rekey, we never present an interactive host key
@@ -7363,6 +7574,12 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
      */
     freebn(s->K);
 
+    /*
+     * Update the specials menu to list the remaining uncertified host
+     * keys.
+     */
+    update_specials_menu(ssh->frontend);
+
     /*
      * Key exchange is over. Loop straight back round if we have a
      * deferred rekey reason.
@@ -11004,6 +11221,9 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
     ssh->connshare = NULL;
     ssh->attempting_connshare = FALSE;
     ssh->session_started = FALSE;
+    ssh->specials = NULL;
+    ssh->n_uncert_hostkeys = 0;
+    ssh->cross_certifying = FALSE;
 
     *backend_handle = ssh;
 
@@ -11151,6 +11371,7 @@ static void ssh_free(void *handle)
     sfree(ssh->v_s);
     sfree(ssh->fullhostname);
     sfree(ssh->hostkey_str);
+    sfree(ssh->specials);
     if (ssh->crcda_ctx) {
 	crcda_free_context(ssh->crcda_ctx);
 	ssh->crcda_ctx = NULL;
@@ -11364,19 +11585,24 @@ static const struct telnet_special *ssh_get_specials(void *handle)
     static const struct telnet_special specials_end[] = {
 	{NULL, TS_EXITMENU}
     };
-    /* XXX review this length for any changes: */
-    static struct telnet_special ssh_specials[lenof(ssh2_ignore_special) +
-					      lenof(ssh2_rekey_special) +
-					      lenof(ssh2_session_specials) +
-					      lenof(specials_end)];
+
+    struct telnet_special *specials = NULL;
+    int nspecials = 0, specialsize = 0;
+
     Ssh ssh = (Ssh) handle;
-    int i = 0;
-#define ADD_SPECIALS(name) \
-    do { \
-	assert((i + lenof(name)) <= lenof(ssh_specials)); \
-	memcpy(&ssh_specials[i], name, sizeof name); \
-	i += lenof(name); \
-    } while(0)
+
+    sfree(ssh->specials);
+
+#define ADD_SPECIALS(name) do                                           \
+    {                                                                   \
+        int len = lenof(name);                                          \
+        if (nspecials + len > specialsize) {                            \
+            specialsize = (nspecials + len) * 5 / 4 + 32;               \
+            specials = sresize(specials, specialsize, struct telnet_special); \
+        }                                                               \
+	memcpy(specials+nspecials, name, len*sizeof(struct telnet_special)); \
+        nspecials += len;                                               \
+    } while (0)
 
     if (ssh->version == 1) {
 	/* Don't bother offering IGNORE if we've decided the remote
@@ -11391,11 +11617,37 @@ static const struct telnet_special *ssh_get_specials(void *handle)
 	    ADD_SPECIALS(ssh2_rekey_special);
 	if (ssh->mainchan)
 	    ADD_SPECIALS(ssh2_session_specials);
+
+        if (ssh->n_uncert_hostkeys) {
+            static const struct telnet_special uncert_start[] = {
+                {NULL, TS_SEP},
+                {"Cache new host key type", TS_SUBMENU},
+            };
+            static const struct telnet_special uncert_end[] = {
+                {NULL, TS_EXITMENU},
+            };
+            int i;
+
+            ADD_SPECIALS(uncert_start);
+            for (i = 0; i < ssh->n_uncert_hostkeys; i++) {
+                struct telnet_special uncert[1];
+                const struct ssh_signkey *alg =
+                    hostkey_algs[ssh->uncert_hostkeys[i]].alg;
+                uncert[0].name = alg->name;
+                uncert[0].code = TS_LOCALSTART + ssh->uncert_hostkeys[i];
+                ADD_SPECIALS(uncert);
+            }
+            ADD_SPECIALS(uncert_end);
+        }
     } /* else we're not ready yet */
 
-    if (i) {
+    if (nspecials)
 	ADD_SPECIALS(specials_end);
-	return ssh_specials;
+
+    ssh->specials = specials;
+
+    if (nspecials) {
+        return specials;
     } else {
 	return NULL;
     }
@@ -11447,6 +11699,13 @@ static void ssh_special(void *handle, Telnet_Special code)
             ssh->version == 2) {
 	    do_ssh2_transport(ssh, "at user request", -1, NULL);
 	}
+    } else if (code >= TS_LOCALSTART) {
+        ssh->hostkey = hostkey_algs[code - TS_LOCALSTART].alg;
+        ssh->cross_certifying = TRUE;
+	if (!ssh->kex_in_progress && !ssh->bare_connection &&
+            ssh->version == 2) {
+	    do_ssh2_transport(ssh, "cross-certifying new host key", -1, NULL);
+	}
     } else if (code == TS_BRK) {
 	if (ssh->state == SSH_STATE_CLOSED
 	    || ssh->state == SSH_STATE_PREPACKET) return;

+ 1 - 0
source/putty/ssh.h

@@ -155,6 +155,7 @@ struct ec_curve {
 const struct ssh_signkey *ec_alg_by_oid(int len, const void *oid,
                                         const struct ec_curve **curve);
 const unsigned char *ec_alg_oid(const struct ssh_signkey *alg, int *oidlen);
+extern const int ec_nist_curve_lengths[], n_ec_nist_curve_lengths;
 const int ec_nist_alg_and_curve_by_bits(int bits,
                                         const struct ec_curve **curve,
                                         const struct ssh_signkey **alg);

+ 3 - 0
source/putty/sshecc.c

@@ -2937,6 +2937,9 @@ const unsigned char *ec_alg_oid(const struct ssh_signkey *alg,
     return extra->oid;
 }
 
+const int ec_nist_curve_lengths[] = { 256, 384, 521 };
+const int n_ec_nist_curve_lengths = lenof(ec_nist_curve_lengths);
+
 const int ec_nist_alg_and_curve_by_bits(int bits,
                                         const struct ec_curve **curve,
                                         const struct ssh_signkey **alg)

+ 3 - 3
source/putty/version.h

@@ -1,5 +1,5 @@
 /* Generated by automated build script */
 #define SNAPSHOT
-#define TEXTVER "Development snapshot 2016-03-06.8e41e0a"
-#define SSHVER "PuTTY-Snapshot-2016-03-06.8e41e0a"
-#define BINARY_VERSION 0,67,1006,0
+#define TEXTVER "Development snapshot 2016-03-31.7f3c956"
+#define SSHVER "PuTTY-Snapshot-2016-03-31.7f3c956"
+#define BINARY_VERSION 0,67,1031,0