]> asedeno.scripts.mit.edu Git - PuTTY.git/commitdiff
New option to manually configure the expected host key(s).
authorSimon Tatham <anakin@pobox.com>
Tue, 9 Sep 2014 11:46:24 +0000 (11:46 +0000)
committerSimon Tatham <anakin@pobox.com>
Tue, 9 Sep 2014 11:46:24 +0000 (11:46 +0000)
This option is available from the command line as '-hostkey', and is
also configurable through the GUI. When enabled, it completely
replaces all of the automated host key management: the server's host
key will be checked against the manually configured list, and the
connection will be allowed or disconnected on that basis, and the host
key store in the registry will not be either consulted or updated.

The main aim is to provide a means of automatically running Plink,
PSCP or PSFTP deep inside Windows services where HKEY_CURRENT_USER
isn't available to have stored the right host key in. But it also
permits you to specify a list of multiple host keys, which means a
second use case for the same mechanism will probably be round-robin
DNS names that select one of several servers with different host keys.

Host keys can be specified as the standard MD5 fingerprint or as an
SSH-2 base64 blob, and are canonicalised on input. (The base64 blob is
more unwieldy, especially with Windows command-line length limits, but
provides a means of specifying the _whole_ public key in case you
don't trust MD5. I haven't bothered to provide an analogous mechanism
for SSH-1, on the basis that anyone worrying about MD5 should have
stopped using SSH-1 already!)

[originally from svn r10220]

cmdline.c
config.c
doc/config.but
doc/faq.but
doc/using.but
misc.c
misc.h
putty.h
settings.c
ssh.c
windows/winhelp.h

index 0c7ef4c91c98ad721403b186d818881587600a16..327a585a4580ed0ebada38e63a3059cb8108142a 100644 (file)
--- a/cmdline.c
+++ b/cmdline.c
@@ -236,6 +236,21 @@ int cmdline_process_param(char *p, char *value, int need_save, Conf *conf)
        SAVEABLE(0);
        conf_set_str(conf, CONF_loghost, value);
     }
+    if (!strcmp(p, "-hostkey")) {
+        char *dup;
+       RETURN(2);
+       UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+       SAVEABLE(0);
+        dup = dupstr(value);
+        if (!validate_manual_hostkey(dup)) {
+            cmdline_error("'%s' is not a valid format for a manual host "
+                          "key specification", value);
+            sfree(dup);
+            return ret;
+        }
+       conf_set_str_str(conf, CONF_ssh_manual_hostkeys, dup, "");
+        sfree(dup);
+    }
     if ((!strcmp(p, "-L") || !strcmp(p, "-R") || !strcmp(p, "-D"))) {
        char type, *q, *qq, *key, *val;
        RETURN(2);
index e4cfabf86f9ce47e6ecaccf3864617e4b85e433d..bfa9071bcfc54d29ca6051060e04c526a72e86ac 100644 (file)
--- a/config.c
+++ b/config.c
@@ -1225,6 +1225,73 @@ static void portfwd_handler(union control *ctrl, void *dlg,
     }
 }
 
+struct manual_hostkey_data {
+    union control *addbutton, *rembutton, *listbox, *keybox;
+};
+
+static void manual_hostkey_handler(union control *ctrl, void *dlg,
+                                   void *data, int event)
+{
+    Conf *conf = (Conf *)data;
+    struct manual_hostkey_data *mh =
+       (struct manual_hostkey_data *)ctrl->generic.context.p;
+
+    if (event == EVENT_REFRESH) {
+       if (ctrl == mh->listbox) {
+           char *key, *val;
+           dlg_update_start(ctrl, dlg);
+           dlg_listbox_clear(ctrl, dlg);
+           for (val = conf_get_str_strs(conf, CONF_ssh_manual_hostkeys,
+                                         NULL, &key);
+                val != NULL;
+                val = conf_get_str_strs(conf, CONF_ssh_manual_hostkeys,
+                                         key, &key)) {
+               dlg_listbox_add(ctrl, dlg, key);
+           }
+           dlg_update_done(ctrl, dlg);
+       }
+    } else if (event == EVENT_ACTION) {
+       if (ctrl == mh->addbutton) {
+           char *key;
+
+           key = dlg_editbox_get(mh->keybox, dlg);
+           if (!*key) {
+               dlg_error_msg(dlg, "You need to specify a host key or "
+                              "fingerprint");
+               sfree(key);
+               return;
+           }
+
+            if (!validate_manual_hostkey(key)) {
+               dlg_error_msg(dlg, "Host key is not in a valid format");
+            } else if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys,
+                                            key)) {
+               dlg_error_msg(dlg, "Specified host key is already listed");
+           } else {
+               conf_set_str_str(conf, CONF_ssh_manual_hostkeys, key, "");
+           }
+
+           sfree(key);
+           dlg_refresh(mh->listbox, dlg);
+       } else if (ctrl == mh->rembutton) {
+           int i = dlg_listbox_index(mh->listbox, dlg);
+           if (i < 0) {
+               dlg_beep(dlg);
+           } else {
+               char *key;
+
+               key = conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, i);
+               if (key) {
+                   dlg_editbox_set(mh->keybox, dlg, key);
+                   /* And delete it */
+                   conf_del_str_str(conf, CONF_ssh_manual_hostkeys, key);
+               }
+           }
+           dlg_refresh(mh->listbox, dlg);
+       }
+    }
+}
+
 void setup_config_box(struct controlbox *b, int midsession,
                      int protocol, int protcfginfo)
 {
@@ -1235,6 +1302,7 @@ void setup_config_box(struct controlbox *b, int midsession,
     struct ttymodes_data *td;
     struct environ_data *ed;
     struct portfwd_data *pfd;
+    struct manual_hostkey_data *mh;
     union control *c;
     char *str;
 
@@ -2163,6 +2231,42 @@ void setup_config_box(struct controlbox *b, int midsession,
                         I(16));
            ctrl_text(s, "(Use 1M for 1 megabyte, 1G for 1 gigabyte etc)",
                      HELPCTX(ssh_kex_repeat));
+
+           s = ctrl_getset(b, "Connection/SSH/Kex", "hostkeys",
+                           "Manually configure host keys for this connection");
+
+            ctrl_columns(s, 2, 75, 25);
+            c = ctrl_text(s, "Host keys or fingerprints to accept:",
+                          HELPCTX(ssh_kex_manual_hostkeys));
+            c->generic.column = 0;
+            /* You want to select from the list, _then_ hit Remove. So
+             * tab order should be that way round. */
+            mh = (struct manual_hostkey_data *)
+                ctrl_alloc(b,sizeof(struct manual_hostkey_data));
+            mh->rembutton = ctrl_pushbutton(s, "Remove", 'r',
+                                            HELPCTX(ssh_kex_manual_hostkeys),
+                                            manual_hostkey_handler, P(mh));
+            mh->rembutton->generic.column = 1;
+            mh->rembutton->generic.tabdelay = 1;
+            mh->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT,
+                                       HELPCTX(ssh_kex_manual_hostkeys),
+                                       manual_hostkey_handler, P(mh));
+            /* This list box can't be very tall, because there's not
+             * much room in the pane on Windows at least. This makes
+             * it become really unhelpful if a horizontal scrollbar
+             * appears, so we suppress that. */
+            mh->listbox->listbox.height = 2;
+            mh->listbox->listbox.hscroll = FALSE;
+            ctrl_tabdelay(s, mh->rembutton);
+           mh->keybox = ctrl_editbox(s, "Key", 'k', 80,
+                                      HELPCTX(ssh_kex_manual_hostkeys),
+                                      manual_hostkey_handler, P(mh), P(NULL));
+            mh->keybox->generic.column = 0;
+            mh->addbutton = ctrl_pushbutton(s, "Add key", 'y',
+                                            HELPCTX(ssh_kex_manual_hostkeys),
+                                            manual_hostkey_handler, P(mh));
+            mh->addbutton->generic.column = 1;
+            ctrl_columns(s, 1, 100);
        }
 
        if (!midsession || protcfginfo != 1) {
index f355a489cfdd0e499dad73ce58bfbf1963ed4fa5..7d93402c92649c5648f7f7a377cb229381e5bd28 100644 (file)
@@ -2466,6 +2466,56 @@ 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.
 
+\S{config-ssh-kex-manual-hostkeys} \ii{Manually configuring host keys}
+
+\cfg{winhelp-topic}{ssh.kex.manualhostkeys}
+
+In some situations, if PuTTY's automated host key management is not
+doing what you need, you might need to manually configure PuTTY to
+accept a specific host key, or one of a specific set of host keys.
+
+One reason why you might want to do this is because the host name
+PuTTY is connecting to is using round-robin DNS to return one of
+multiple actual servers, and they all have different host keys. In
+that situation, you might need to configure PuTTY to accept any of a
+list of host keys for the possible servers, while still rejecting any
+key not in that list.
+
+Another reason is if PuTTY's automated host key management is
+completely unavailable, e.g. because PuTTY (or Plink or PSFTP, etc) is
+running in a Windows environment without access to the Registry. In
+that situation, you will probably want to use the \cw{-hostkey}
+command-line option to configure the expected host key(s); see FIXME.
+
+To configure manual host keys via the GUI, enter some text describing
+the host key into the edit box in the \q{Manually configure host keys
+for this connection} container, and press the \q{Add} button. The text
+will appear in the {q Host keys or fingerprints to accept} list box.
+You can remove keys again with the \q{Remove} button.
+
+The text describing a host key can be in one of the following formats:
+
+\b An MD5-based host key fingerprint of the form displayed in PuTTY's
+Event Log and host key dialog boxes, i.e. sixteen 2-digit hex numbers
+separated by colons.
+
+\b A base64-encoded blob describing an SSH-2 public key in the
+standard way. This can be found in OpenSSH's one-line public key
+format, or by concatenating all the lines of the public key section in
+one of PuTTY's \cw{.ppk} files. Alternatively, you can load a key into
+PuTTYgen, and paste out the OpenSSH-format public key line it
+displays.
+
+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}.
+
+If the box is empty (as it usually is), then PuTTY's automated host
+key management will work as normal.
+
 \H{config-ssh-encryption} The Cipher panel
 
 \cfg{winhelp-topic}{ssh.ciphers}
index a34973bdb7ba76bf14250bea1c5d01729ce25b59..b8650df95d1f8952e70c19ac2cd9d7955d02ec6b 100644 (file)
@@ -151,13 +151,14 @@ military-strength cipher. That insignificant host key prompt really
 does make \e{that} much difference.
 
 If you're having a specific problem with host key checking - perhaps
-you want an automated batch job to make use of PSCP or Plink, and
-the interactive host key prompt is hanging the batch process - then
-the right way to fix it is to add the correct host key to the
-Registry in advance. That way, you retain the \e{important} feature
-of host key checking: the right key will be accepted and the wrong
-ones will not. Adding an option to turn host key checking off
-completely is the wrong solution and we will not do it.
+you want an automated batch job to make use of PSCP or Plink, and the
+interactive host key prompt is hanging the batch process - then the
+right way to fix it is to add the correct host key to the Registry in
+advance, or if the Registry is not available, to use the \cw{-hostkey}
+command-line option. That way, you retain the \e{important} feature of
+host key checking: the right key will be accepted and the wrong ones
+will not. Adding an option to turn host key checking off completely is
+the wrong solution and we will not do it.
 
 If you have host keys available in the common \i\c{known_hosts} format,
 we have a script called 
index 27a1c2a83b161d648ec8da34ffe5bd7a8976fb19..fec03d32c6050a2eaa099ca92ad64b91b5fa5a1a 100644 (file)
@@ -913,6 +913,19 @@ 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.
+
+You can specify this option more than once if you want to configure
+more than one key to be accepted.
+
 \S2{using-cmdline-pgpfp} \i\c{-pgpfp}: display \i{PGP key fingerprint}s
 
 This option causes the PuTTY tools not to run as normal, but instead
diff --git a/misc.c b/misc.c
index fb7cc8c2882df5fd2f99170ec84ce1c2545e9c58..24e42ebac5e77cef0725b9bd5e70dddbd4b51c7a 100644 (file)
--- a/misc.c
+++ b/misc.c
@@ -922,3 +922,100 @@ void smemclr(void *b, size_t n) {
     }
 }
 #endif
+
+/*
+ * Validate a manual host key specification (either entered in the
+ * GUI, or via -hostkey). If valid, we return TRUE, and update 'key'
+ * to contain a canonicalised version of the key string in 'key'
+ * (which is guaranteed to take up at most as much space as the
+ * original version), suitable for putting into the Conf. If not
+ * valid, we return FALSE.
+ */
+int validate_manual_hostkey(char *key)
+{
+    char *p, *q, *r, *s;
+
+    /*
+     * Step through the string word by word, looking for a word that's
+     * in one of the formats we like.
+     */
+    p = key;
+    while ((p += strspn(p, " \t"))[0]) {
+        q = p;
+        p += strcspn(p, " \t");
+        if (p) *p++ = '\0';
+
+        /*
+         * Now q is our word.
+         */
+
+        if (strlen(q) == 16*3 - 1 &&
+            q[strspn(q, "0123456789abcdefABCDEF:")] == 0) {
+            /*
+             * Might be a key fingerprint. Check the colons are in the
+             * right places, and if so, return the same fingerprint
+             * canonicalised into lowercase.
+             */
+            int i;
+            for (i = 0; i < 16; i++)
+                if (q[3*i] == ':' || q[3*i+1] == ':')
+                    goto not_fingerprint; /* sorry */
+            for (i = 0; i < 15; i++)
+                if (q[3*i+2] != ':')
+                    goto not_fingerprint; /* sorry */
+            for (i = 0; i < 16*3 - 1; i++)
+                key[i] = tolower(q[i]);
+            key[16*3 - 1] = '\0';
+            return TRUE;
+        }
+      not_fingerprint:;
+
+        /*
+         * Before we check for a public-key blob, trim newlines out of
+         * the middle of the word, in case someone's managed to paste
+         * in a public-key blob _with_ them.
+         */
+        for (r = s = q; *r; r++)
+            if (*r != '\n' && *r != '\r')
+                *s++ = *r;
+        *s = '\0';
+
+        if (strlen(q) % 4 == 0 && strlen(q) > 2*4 &&
+            q[strspn(q, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                     "abcdefghijklmnopqrstuvwxyz+/=")] == 0) {
+            /*
+             * Might be a base64-encoded SSH-2 public key blob. Check
+             * that it starts with a sensible algorithm string. No
+             * canonicalisation is necessary for this string type.
+             *
+             * The algorithm string must be at most 64 characters long
+             * (RFC 4251 section 6).
+             */
+            unsigned char decoded[6];
+            unsigned alglen;
+            int minlen;
+            int len = 0;
+
+            len += base64_decode_atom(q, decoded+len);
+            if (len < 3)
+                goto not_ssh2_blob;    /* sorry */
+            len += base64_decode_atom(q+4, decoded+len);
+            if (len < 4)
+                goto not_ssh2_blob;    /* sorry */
+
+            alglen = GET_32BIT_MSB_FIRST(decoded);
+            if (alglen > 64)
+                goto not_ssh2_blob;    /* sorry */
+
+            minlen = ((alglen + 4) + 2) / 3;
+            if (strlen(q) < minlen)
+                goto not_ssh2_blob;    /* sorry */
+
+            strcpy(key, q);
+            return TRUE;
+        }
+      not_ssh2_blob:;
+    }
+
+    return FALSE;
+}
diff --git a/misc.h b/misc.h
index 306b4a1ec4cb299845852b21afcfa1deccb7eb2a..a361d706b45d40fd8c7908a97fb17520c67f7dc3 100644 (file)
--- a/misc.h
+++ b/misc.h
@@ -60,6 +60,8 @@ void bufchain_prefix(bufchain *ch, void **data, int *len);
 void bufchain_consume(bufchain *ch, int len);
 void bufchain_fetch(bufchain *ch, void *data, int len);
 
+int validate_manual_hostkey(char *key);
+
 struct tm ltime(void);
 
 void smemclr(void *b, size_t len);
diff --git a/putty.h b/putty.h
index a6578ae0c4c1e9c5eef1d15d6ecae7708a806268..ff0f3156a111eca11a206ab82c6917e79870c1de 100644 (file)
--- a/putty.h
+++ b/putty.h
@@ -848,6 +848,12 @@ void cleanup_exit(int);
     X(INT, NONE, ssh_connection_sharing) \
     X(INT, NONE, ssh_connection_sharing_upstream) \
     X(INT, NONE, ssh_connection_sharing_downstream) \
+    /*
+     * ssh_manual_hostkeys is conceptually a set rather than a
+     * dictionary: the string subkeys are the important thing, and the
+     * actual values to which those subkeys map are all "".
+     */ \
+    X(STR, STR, ssh_manual_hostkeys) \
     /* Options for pterm. Should split out into platform-dependent part. */ \
     X(INT, NONE, stamp_utmp) \
     X(INT, NONE, login_shell) \
index aff6bf64673c1a3f6c8beff915df30b132a1e4a6..898c0dabc97ea18841962689dfc6f71a67ece2f7 100644 (file)
@@ -650,6 +650,7 @@ void save_open_settings(void *sesskey, Conf *conf)
     write_setting_i(sesskey, "ConnectionSharing", conf_get_int(conf, CONF_ssh_connection_sharing));
     write_setting_i(sesskey, "ConnectionSharingUpstream", conf_get_int(conf, CONF_ssh_connection_sharing_upstream));
     write_setting_i(sesskey, "ConnectionSharingDownstream", conf_get_int(conf, CONF_ssh_connection_sharing_downstream));
+    wmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys, FALSE);
 }
 
 void load_settings(char *section, Conf *conf)
@@ -996,6 +997,7 @@ void load_open_settings(void *sesskey, Conf *conf)
     gppi(sesskey, "ConnectionSharing", 0, conf, CONF_ssh_connection_sharing);
     gppi(sesskey, "ConnectionSharingUpstream", 1, conf, CONF_ssh_connection_sharing_upstream);
     gppi(sesskey, "ConnectionSharingDownstream", 1, conf, CONF_ssh_connection_sharing_downstream);
+    gppmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys);
 }
 
 void do_defaults(char *session, Conf *conf)
diff --git a/ssh.c b/ssh.c
index 893321e2b5a5f396004820f0f68aefcb460d0b31..57ee2e8acd6d387d9070194e597ad67224233bfe 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -3677,6 +3677,59 @@ static void ssh_disconnect(Ssh ssh, char *client_reason, char *wire_reason,
     sfree(error);
 }
 
+int verify_ssh_manual_host_key(Ssh ssh, const char *fingerprint,
+                               const struct ssh_signkey *ssh2keytype,
+                               void *ssh2keydata)
+{
+    if (!conf_get_str_nthstrkey(ssh->conf, CONF_ssh_manual_hostkeys, 0)) {
+        return -1;                     /* no manual keys configured */
+    }
+
+    if (fingerprint) {
+        /*
+         * The fingerprint string we've been given will have things
+         * like 'ssh-rsa 2048' at the front of it. Strip those off and
+         * narrow down to just the colon-separated hex block at the
+         * end of the string.
+         */
+        const char *p = strrchr(fingerprint, ' ');
+        fingerprint = p ? p+1 : fingerprint;
+        /* Quick sanity checks, including making sure it's in lowercase */
+        assert(strlen(fingerprint) == 16*3 - 1);
+        assert(fingerprint[2] == ':');
+        assert(fingerprint[strspn(fingerprint, "0123456789abcdef:")] == 0);
+
+        if (conf_get_str_str_opt(ssh->conf, CONF_ssh_manual_hostkeys,
+                                 fingerprint))
+            return 1;                  /* success */
+    }
+
+    if (ssh2keydata) {
+        /*
+         * Construct the base64-encoded public key blob and see if
+         * that's listed.
+         */
+        unsigned char *binblob;
+        char *base64blob;
+        int binlen, atoms, i;
+        binblob = ssh2keytype->public_blob(ssh2keydata, &binlen);
+        atoms = (binlen + 2) / 3;
+        base64blob = snewn(atoms * 4 + 1, char);
+        for (i = 0; i < atoms; i++)
+            base64_encode_atom(binblob + 3*i, binlen - 3*i, base64blob + 4*i);
+        base64blob[atoms * 4] = '\0';
+        sfree(binblob);
+        if (conf_get_str_str_opt(ssh->conf, CONF_ssh_manual_hostkeys,
+                                 base64blob)) {
+            sfree(base64blob);
+            return 1;                  /* success */
+        }
+        sfree(base64blob);
+    }
+
+    return 0;
+}
+
 /*
  * Handle the key exchange and user authentication phases.
  */
@@ -3800,29 +3853,36 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
        rsastr_fmt(keystr, &s->hostkey);
        rsa_fingerprint(fingerprint, sizeof(fingerprint), &s->hostkey);
 
-        ssh_set_frozen(ssh, 1);
-       s->dlgret = verify_ssh_host_key(ssh->frontend,
-                                        ssh->savedhost, ssh->savedport,
-                                        "rsa", keystr, fingerprint,
-                                        ssh_dialog_callback, ssh);
-       sfree(keystr);
-        if (s->dlgret < 0) {
-            do {
-                crReturn(0);
-                if (pktin) {
-                    bombout(("Unexpected data from server while waiting"
-                             " for user host key response"));
-                    crStop(0);
-                }
-            } while (pktin || inlen > 0);
-            s->dlgret = ssh->user_response;
-        }
-        ssh_set_frozen(ssh, 0);
+        /* First check against manually configured host keys. */
+        s->dlgret = verify_ssh_manual_host_key(ssh, fingerprint, NULL, NULL);
+        if (s->dlgret == 0) {          /* did not match */
+            bombout(("Host key did not appear in manually configured list"));
+            crStop(0);
+        } else if (s->dlgret < 0) { /* none configured; use standard handling */
+            ssh_set_frozen(ssh, 1);
+            s->dlgret = verify_ssh_host_key(ssh->frontend,
+                                            ssh->savedhost, ssh->savedport,
+                                            "rsa", keystr, fingerprint,
+                                            ssh_dialog_callback, ssh);
+            sfree(keystr);
+            if (s->dlgret < 0) {
+                do {
+                    crReturn(0);
+                    if (pktin) {
+                        bombout(("Unexpected data from server while waiting"
+                                 " for user host key response"));
+                        crStop(0);
+                    }
+                } 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 verification",
-                          NULL, 0, TRUE);
-           crStop(0);
+            if (s->dlgret == 0) {
+                ssh_disconnect(ssh, "User aborted at host key verification",
+                               NULL, 0, TRUE);
+                crStop(0);
+            }
         }
     }
 
@@ -6721,31 +6781,39 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
          * checked the signature of the exchange hash.)
          */
         s->fingerprint = ssh->hostkey->fingerprint(s->hkey);
-        ssh_set_frozen(ssh, 1);
-        s->dlgret = verify_ssh_host_key(ssh->frontend,
-                                        ssh->savedhost, ssh->savedport,
-                                        ssh->hostkey->keytype, s->keystr,
-                                        s->fingerprint,
-                                        ssh_dialog_callback, ssh);
-        if (s->dlgret < 0) {
-            do {
-                crReturnV;
-                if (pktin) {
-                    bombout(("Unexpected data from server while waiting"
-                             " for user host key 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 verification", NULL,
-                           0, TRUE);
-            crStopV;
-        }
         logevent("Host key fingerprint is:");
         logevent(s->fingerprint);
+        /* First check against manually configured host keys. */
+        s->dlgret = verify_ssh_manual_host_key(ssh, s->fingerprint,
+                                               ssh->hostkey, s->hkey);
+        if (s->dlgret == 0) {          /* did not match */
+            bombout(("Host key did not appear in manually configured list"));
+            crStopV;
+        } else if (s->dlgret < 0) { /* none configured; use standard handling */
+            ssh_set_frozen(ssh, 1);
+            s->dlgret = verify_ssh_host_key(ssh->frontend,
+                                            ssh->savedhost, ssh->savedport,
+                                            ssh->hostkey->keytype, s->keystr,
+                                            s->fingerprint,
+                                            ssh_dialog_callback, ssh);
+            if (s->dlgret < 0) {
+                do {
+                    crReturnV;
+                    if (pktin) {
+                        bombout(("Unexpected data from server while waiting"
+                                 " for user host key response"));
+                        crStopV;
+                    }
+                } while (pktin || inlen > 0);
+                s->dlgret = ssh->user_response;
+            }
+            ssh_set_frozen(ssh, 0);
+            if (s->dlgret == 0) {
+                ssh_disconnect(ssh, "Aborted at host key verification", NULL,
+                               0, TRUE);
+                crStopV;
+            }
+        }
         sfree(s->fingerprint);
         /*
          * Save this host key, to check against the one presented in
index dabd8b825baec3d984e65a7810a2a0d5a436de1d..fc10a8f4d5b895d1f762d61a3ad95f897f6ed6c0 100644 (file)
 #define WINHELP_CTX_ssh_share "ssh.sharing:config-ssh-sharing"
 #define WINHELP_CTX_ssh_kexlist "ssh.kex.order:config-ssh-kex-order"
 #define WINHELP_CTX_ssh_kex_repeat "ssh.kex.repeat:config-ssh-kex-rekey"
+#define WINHELP_CTX_ssh_kex_manual_hostkeys "ssh.kex.manualhostkeys:config-ssh-kex-manual-hostkeys"
 #define WINHELP_CTX_ssh_auth_bypass "ssh.auth.bypass:config-ssh-noauth"
 #define WINHELP_CTX_ssh_auth_banner "ssh.auth.banner:config-ssh-banner"
 #define WINHELP_CTX_ssh_auth_privkey "ssh.auth.privkey:config-ssh-privkey"