}
}
+static void kexlist_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ Config *cfg = (Config *)data;
+ if (event == EVENT_REFRESH) {
+ int i;
+
+ static const struct { char *s; int k; } kexes[] = {
+ { "Diffie-Hellman group 1", KEX_DHGROUP1 },
+ { "Diffie-Hellman group 14", KEX_DHGROUP14 },
+ { "Diffie-Hellman group exchange", KEX_DHGEX },
+ { "-- warn below here --", KEX_WARN }
+ };
+
+ /* Set up the "kex preference" box. */
+ /* (kexlist assumed to contain all algorithms) */
+ dlg_update_start(ctrl, dlg);
+ dlg_listbox_clear(ctrl, dlg);
+ for (i = 0; i < KEX_MAX; i++) {
+ int k = cfg->ssh_kexlist[i];
+ int j;
+ char *kstr = NULL;
+ for (j = 0; j < (sizeof kexes) / (sizeof kexes[0]); j++) {
+ if (kexes[j].k == k) {
+ kstr = kexes[j].s;
+ break;
+ }
+ }
+ dlg_listbox_addwithid(ctrl, dlg, kstr, k);
+ }
+ dlg_update_done(ctrl, dlg);
+
+ } else if (event == EVENT_VALCHANGE) {
+ int i;
+
+ /* Update array to match the list box. */
+ for (i=0; i < KEX_MAX; i++)
+ cfg->ssh_kexlist[i] = dlg_listbox_getid(ctrl, dlg, i);
+
+ }
+}
+
static void printerbox_handler(union control *ctrl, void *dlg,
void *data, int event)
{
I(offsetof(Config,ssh2_des_cbc)));
/*
+ * The Connection/SSH/Kex panel.
+ */
+ ctrl_settitle(b, "Connection/SSH/Kex",
+ "Options controlling SSH key exchange");
+
+ s = ctrl_getset(b, "Connection/SSH/Kex", "main",
+ "Key exchange algorithm options");
+ c = ctrl_draglist(s, "Algorithm selection policy", 's',
+ HELPCTX(ssh_kexlist),
+ kexlist_handler, P(NULL));
+ c->listbox.height = 5;
+
+#if 0
+ s = ctrl_getset(b, "Connection/SSH/Kex", "repeat",
+ "Options controlling key re-exchange");
+ /* FIXME: at least time and data size */
+#endif
+
+ /*
* The Connection/SSH/Auth panel.
*/
ctrl_settitle(b, "Connection/SSH/Auth",
ctrl_droplist(s, "Requires padding on SSH2 RSA signatures", 'p', 20,
HELPCTX(ssh_bugs_rsapad2),
sshbug_handler, I(offsetof(Config,sshbug_rsapad2)));
- ctrl_droplist(s, "Chokes on Diffie-Hellman group exchange", 'd', 20,
- HELPCTX(ssh_bugs_dhgex2),
- sshbug_handler, I(offsetof(Config,sshbug_dhgex2)));
ctrl_droplist(s, "Misuses the session ID in PK auth", 'n', 20,
HELPCTX(ssh_bugs_pksessid2),
sshbug_handler, I(offsetof(Config,sshbug_pksessid2)));
SSH 2} option; by default this is disabled and PuTTY will stick to
recommended ciphers.
+\H{config-ssh-kex} The Kex panel
+
+\# FIXME: This whole section is draft. Feel free to revise.
+
+The Kex panel (short for \q{key exchange}) allows you to configure
+options related to SSH-2 key exchange.
+
+Key exchange occurs at the start of an SSH connection (and
+occasionally thereafter); it establishes a shared secret that is used
+as the basis for all of SSH's security features. It is therefore very
+important for the security of the connection that the key exchange is
+secure.
+
+Key exchange is a cryptographically intensive process; if either the
+client or the server is a relatively slow machine, the slower methods
+may take several tens of seconds to complete.
+
+If connection startup is too slow, or the connection hangs
+periodically, you may want to try changing these settings.
+
+If you don't understand what any of this means, it's safe to leave
+these settings alone.
+
+This entire panel is only relevant to SSH protocol version 2; none of
+these settings affect SSH-1 at all.
+
+\S{config-ssh-kex-order} Key exchange algorithm selection
+
+\cfg{winhelp-topic}{ssh.kex.order}
+
+PuTTY supports a variety of SSH-2 key exchange methods, and allows you
+to choose which one you prefer to use; configuration is similar to
+cipher selection (see \k{config-ssh-encryption}).
+
+PuTTY currently supports the following varieties of Diffie-Hellman key
+exchange:
+
+\b \q{Group 14}: a well-known 2048-bit group.
+
+\b \q{Group 1}: a well-known 1024-bit group. This is less secure
+\#{FIXME better words} than group 14, but may be faster with slow
+client or server machines, and may be the only method supported by
+older server software.
+
+\b \q{Group exchange}: with this method, instead of using a fixed
+group, PuTTY requests that the server suggest a group to use for key
+exchange; the server can avoid groups known to be weak, and possibly
+invent new ones over time, without any changes required to PuTTY's
+configuration. We recommend use of this method, if possible.
+
+If the first algorithm 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}).
+
+\# [Repeat key exchange bumph when config is added:] If the session
+key negotiated at connection startup is used too much or for too long,
+it may become feasible to mount attacks against the SSH connection.
+Therefore, the SSH protocol specifies that a new key exchange should
+take place every so often.
+
+\# While this renegotiation is taking place, no data can pass through
+the SSH connection, so it may appear to \q{freeze}. (The occurrence of
+repeat key exchange is noted in the Event Log; see
+\k{using-eventlog}.) Usually the same algorithm is used as at the
+start of the connection, with a similar overhead.
+
+\# [When options are added to frob how often this happens, we should
+hardcode the values recommended by the drafts -- 1 hour, 1GB -- in
+this documentation, in case PuTTY's defaults are obscured by Default
+Settings etc. Assuming we think they're good advice, that is.]
+
\H{config-ssh-auth} The Auth panel
The Auth panel allows you to configure authentication options for
This is an SSH2-specific bug.
-\S{config-ssh-bug-dhgex} \q{Chokes on Diffie-Hellman group exchange}
-
-\cfg{winhelp-topic}{ssh.bugs.dhgex2}
-
-We have anecdotal evidence that some SSH servers claim to be able to
-perform Diffie-Hellman group exchange, but fail to actually do so
-when PuTTY tries to. If your SSH2 sessions spontaneously close
-immediately after opening the PuTTY window, it might be worth
-enabling the workaround for this bug to see if it helps.
-
-We have no hard evidence that any specific version of specific
-server software reliably demonstrates this bug. Therefore, PuTTY
-will never \e{assume} a server has this bug; if you want the
-workaround, you need to enable it manually.
-
-This is an SSH2-specific bug.
-
\S{config-ssh-bug-pksessid2} \q{Misuses the session ID in PK auth}
\cfg{winhelp-topic}{ssh.bugs.pksessid2}
-/* $Id: mac.c,v 1.59 2003/05/10 12:27:38 ben Exp $ */
+/* $Id$ */
/*
* Copyright (c) 1999, 2003 Ben Harris
* All rights reserved.
}
}
-void askcipher(void *frontend, char *ciphername, int cs)
+void askalg(void *frontend, const char *algtype, const char *algname)
{
}
enum {
/*
+ * SSH-2 key exchange algorithms
+ */
+ KEX_WARN,
+ KEX_DHGROUP1,
+ KEX_DHGROUP14,
+ KEX_DHGEX,
+ KEX_MAX
+};
+
+enum {
+ /*
* SSH ciphers (both SSH1 and SSH2)
*/
CIPHER_WARN, /* pseudo 'cipher' */
* but never for loading/saving */
int nopty;
int compression;
+ int ssh_kexlist[KEX_MAX];
int agentfwd;
int change_username; /* allow username switching in SSH2 */
int ssh_cipherlist[CIPHER_MAX];
/* SSH bug compatibility modes */
int sshbug_ignore1, sshbug_plainpw1, sshbug_rsa1,
sshbug_hmac2, sshbug_derivekey2, sshbug_rsapad2,
- sshbug_dhgex2, sshbug_pksessid2;
+ sshbug_pksessid2;
/* Options for pterm. Should split out into platform-dependent part. */
int stamp_utmp;
int login_shell;
void logevent(void *frontend, const char *);
void verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
char *keystr, char *fingerprint);
-void askcipher(void *frontend, char *ciphername, int cs);
+void askalg(void *frontend, const char *algtype, const char *algname);
int askappend(void *frontend, Filename filename);
/*
*/
struct keyval { char *s; int v; };
+/* The cipher order given here is the default order. */
static const struct keyval ciphernames[] = {
{ "aes", CIPHER_AES },
{ "blowfish", CIPHER_BLOWFISH },
{ "des", CIPHER_DES }
};
+static const struct keyval kexnames[] = {
+ { "dh-gex-sha1", KEX_DHGEX },
+ { "dh-group14-sha1", KEX_DHGROUP14 },
+ { "dh-group1-sha1", KEX_DHGROUP1 },
+ { "WARN", KEX_WARN }
+};
+
static void gpps(void *handle, const char *name, const char *def,
char *val, int len)
{
write_setting_i(sesskey, "ChangeUsername", cfg->change_username);
wprefs(sesskey, "Cipher", ciphernames, CIPHER_MAX,
cfg->ssh_cipherlist);
+ wprefs(sesskey, "KEX", kexnames, KEX_MAX, cfg->ssh_kexlist);
write_setting_i(sesskey, "AuthTIS", cfg->try_tis_auth);
write_setting_i(sesskey, "AuthKI", cfg->try_ki_auth);
write_setting_i(sesskey, "SshNoShell", cfg->ssh_no_shell);
write_setting_i(sesskey, "BugHMAC2", 2-cfg->sshbug_hmac2);
write_setting_i(sesskey, "BugDeriveKey2", 2-cfg->sshbug_derivekey2);
write_setting_i(sesskey, "BugRSAPad2", 2-cfg->sshbug_rsapad2);
- write_setting_i(sesskey, "BugDHGEx2", 2-cfg->sshbug_dhgex2);
write_setting_i(sesskey, "BugPKSessID2", 2-cfg->sshbug_pksessid2);
write_setting_i(sesskey, "StampUtmp", cfg->stamp_utmp);
write_setting_i(sesskey, "LoginShell", cfg->login_shell);
gppi(sesskey, "ChangeUsername", 0, &cfg->change_username);
gprefs(sesskey, "Cipher", "\0",
ciphernames, CIPHER_MAX, cfg->ssh_cipherlist);
+ {
+ /* Backward-compatibility: we used to have an option to
+ * disable gex under the "bugs" panel after one report of
+ * a server which offered it then choked, but we never got
+ * a server version string or any other reports. */
+ char *default_kexes;
+ gppi(sesskey, "BugDHGEx2", 0, &i); i = 2-i;
+ if (i == FORCE_ON)
+ default_kexes = "dh-group14-sha1,dh-group1-sha1,WARN,dh-gex-sha1";
+ else
+ default_kexes = "dh-gex-sha1,dh-group14-sha1,dh-group1-sha1,WARN";
+ gprefs(sesskey, "KEX", default_kexes,
+ kexnames, KEX_MAX, cfg->ssh_kexlist);
+ }
gppi(sesskey, "SshProt", 2, &cfg->sshprot);
gppi(sesskey, "SSH2DES", 0, &cfg->ssh2_des_cbc);
gppi(sesskey, "AuthTIS", 0, &cfg->try_tis_auth);
}
gppi(sesskey, "BugDeriveKey2", 0, &i); cfg->sshbug_derivekey2 = 2-i;
gppi(sesskey, "BugRSAPad2", 0, &i); cfg->sshbug_rsapad2 = 2-i;
- gppi(sesskey, "BugDHGEx2", 0, &i); cfg->sshbug_dhgex2 = 2-i;
gppi(sesskey, "BugPKSessID2", 0, &i); cfg->sshbug_pksessid2 = 2-i;
gppi(sesskey, "StampUtmp", 1, &cfg->stamp_utmp);
gppi(sesskey, "LoginShell", 1, &cfg->login_shell);
#define BUG_CHOKES_ON_RSA 8
#define BUG_SSH2_RSA_PADDING 16
#define BUG_SSH2_DERIVEKEY 32
-#define BUG_SSH2_DH_GEX 64
+/* 64 was BUG_SSH2_DH_GEX, now spare */
#define BUG_SSH2_PK_SESSIONID 128
#define translate(x) if (type == x) return #x
#define SSH_MAX_BACKLOG 32768
#define OUR_V2_WINSIZE 16384
-const static struct ssh_kex *kex_algs[] = {
- &ssh_diffiehellman_gex,
- &ssh_diffiehellman_group14,
- &ssh_diffiehellman_group1,
-};
-
const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss };
static void *nullmac_make_context(void)
ssh->remote_bugs |= BUG_SSH2_PK_SESSIONID;
logevent("We believe remote version has SSH2 public-key-session-ID bug");
}
-
- if (ssh->cfg.sshbug_dhgex2 == FORCE_ON) {
- /*
- * User specified the SSH2 DH GEX bug.
- */
- ssh->remote_bugs |= BUG_SSH2_DH_GEX;
- logevent("We believe remote version has SSH2 DH group exchange bug");
- }
}
/*
/* Warn about chosen cipher if necessary. */
if (warn)
- askcipher(ssh->frontend, cipher_string, 0);
+ askalg(ssh->frontend, "cipher", cipher_string);
}
switch (s->cipher_type) {
int hostkeylen, siglen;
void *hkey; /* actual host key */
unsigned char exchange_hash[20];
+ int n_preferred_kex;
+ const struct ssh_kex *preferred_kex[KEX_MAX];
int n_preferred_ciphers;
const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
const struct ssh_compress *preferred_comp;
{
int i;
/*
+ * Set up the preferred key exchange. (NULL => warn below here)
+ */
+ s->n_preferred_kex = 0;
+ for (i = 0; i < KEX_MAX; i++) {
+ switch (ssh->cfg.ssh_kexlist[i]) {
+ case KEX_DHGEX:
+ s->preferred_kex[s->n_preferred_kex++] =
+ &ssh_diffiehellman_gex;
+ break;
+ case KEX_DHGROUP14:
+ s->preferred_kex[s->n_preferred_kex++] =
+ &ssh_diffiehellman_group14;
+ break;
+ case KEX_DHGROUP1:
+ s->preferred_kex[s->n_preferred_kex++] =
+ &ssh_diffiehellman_group1;
+ break;
+ case CIPHER_WARN:
+ /* Flag for later. Don't bother if it's the last in
+ * the list. */
+ if (i < KEX_MAX - 1) {
+ s->preferred_kex[s->n_preferred_kex++] = NULL;
+ }
+ break;
+ }
+ }
+ }
+
+ {
+ int i;
+ /*
* Set up the preferred ciphers. (NULL => warn below here)
*/
s->n_preferred_ciphers = 0;
begin_key_exchange:
{
- int i, j, cipherstr_started;
+ int i, j, commalist_started;
/*
* Enable queueing of outgoing auth- or connection-layer
ssh2_pkt_addbyte(s->pktout, (unsigned char) random_byte());
/* List key exchange algorithms. */
ssh2_pkt_addstring_start(s->pktout);
- for (i = 0; i < lenof(kex_algs); i++) {
- if (kex_algs[i] == &ssh_diffiehellman_gex &&
- (ssh->remote_bugs & BUG_SSH2_DH_GEX))
- continue;
- ssh2_pkt_addstring_str(s->pktout, kex_algs[i]->name);
- if (i < lenof(kex_algs) - 1)
+ commalist_started = 0;
+ for (i = 0; i < s->n_preferred_kex; i++) {
+ const struct ssh_kex *k = s->preferred_kex[i];
+ if (!k) continue; /* warning flag */
+ if (commalist_started)
ssh2_pkt_addstring_str(s->pktout, ",");
+ ssh2_pkt_addstring_str(s->pktout, s->preferred_kex[i]->name);
+ commalist_started = 1;
}
/* List server host key algorithms. */
ssh2_pkt_addstring_start(s->pktout);
}
/* List client->server encryption algorithms. */
ssh2_pkt_addstring_start(s->pktout);
- cipherstr_started = 0;
+ commalist_started = 0;
for (i = 0; i < s->n_preferred_ciphers; i++) {
const struct ssh2_ciphers *c = s->preferred_ciphers[i];
if (!c) continue; /* warning flag */
for (j = 0; j < c->nciphers; j++) {
- if (cipherstr_started)
+ if (commalist_started)
ssh2_pkt_addstring_str(s->pktout, ",");
ssh2_pkt_addstring_str(s->pktout, c->list[j]->name);
- cipherstr_started = 1;
+ commalist_started = 1;
}
}
/* List server->client encryption algorithms. */
ssh2_pkt_addstring_start(s->pktout);
- cipherstr_started = 0;
+ commalist_started = 0;
for (i = 0; i < s->n_preferred_ciphers; i++) {
const struct ssh2_ciphers *c = s->preferred_ciphers[i];
if (!c) continue; /* warning flag */
for (j = 0; j < c->nciphers; j++) {
- if (cipherstr_started)
+ if (commalist_started)
ssh2_pkt_addstring_str(s->pktout, ",");
ssh2_pkt_addstring_str(s->pktout, c->list[j]->name);
- cipherstr_started = 1;
+ commalist_started = 1;
}
}
/* List client->server MAC algorithms. */
s->sccomp_tobe = NULL;
pktin->savedpos += 16; /* skip garbage cookie */
ssh_pkt_getstring(pktin, &str, &len); /* key exchange algorithms */
- for (i = 0; i < lenof(kex_algs); i++) {
- if (kex_algs[i] == &ssh_diffiehellman_gex &&
- (ssh->remote_bugs & BUG_SSH2_DH_GEX))
- continue;
- if (in_commasep_string(kex_algs[i]->name, str, len)) {
- ssh->kex = kex_algs[i];
+ s->warn = 0;
+ for (i = 0; i < s->n_preferred_kex; i++) {
+ const struct ssh_kex *k = s->preferred_kex[i];
+ if (!k) {
+ s->warn = 1;
+ } else if (in_commasep_string(k->name, str, len)) {
+ ssh->kex = k;
+ }
+ if (ssh->kex) {
+ if (s->warn)
+ askalg(ssh->frontend, "key-exchange algorithm",
+ ssh->kex->name);
break;
}
}
+ if (!ssh->kex) {
+ bombout(("Couldn't agree a key exchange algorithm (available: %s)",
+ str ? str : "(null)"));
+ crStop(0);
+ }
ssh_pkt_getstring(pktin, &str, &len); /* host key algorithms */
for (i = 0; i < lenof(hostkey_algs); i++) {
if (in_commasep_string(hostkey_algs[i]->name, str, len)) {
}
if (s->cscipher_tobe) {
if (s->warn)
- askcipher(ssh->frontend, s->cscipher_tobe->name, 1);
+ askalg(ssh->frontend, "client-to-server cipher",
+ s->cscipher_tobe->name);
break;
}
}
}
if (s->sccipher_tobe) {
if (s->warn)
- askcipher(ssh->frontend, s->sccipher_tobe->name, 2);
+ askalg(ssh->frontend, "server-to-client cipher",
+ s->sccipher_tobe->name);
break;
}
}
}
/*
- * Ask whether the selected cipher is acceptable (since it was
+ * Ask whether the selected algorithm is acceptable (since it was
* below the configured 'warn' threshold).
- * cs: 0 = both ways, 1 = client->server, 2 = server->client
*/
-void askcipher(void *frontend, char *ciphername, int cs)
+void askalg(void *frontend, const char *algtype, const char *algname)
{
static const char msg[] =
- "The first %scipher supported by the server is "
+ "The first %s supported by the server is "
"%s, which is below the configured warning threshold.\n"
"Continue with connection?";
char *text;
int ret;
- text = dupprintf(msg, (cs == 0) ? "" :
- (cs == 1) ? "client-to-server " : "server-to-client ",
- ciphername);
+ text = dupprintf(msg, algtype, algname);
ret = messagebox(GTK_WIDGET(get_window(frontend)),
"PuTTY Security Alert", text,
string_width("Continue with connection?"),
}
/*
- * Ask whether the selected cipher is acceptable (since it was
+ * Ask whether the selected algorithm is acceptable (since it was
* below the configured 'warn' threshold).
- * cs: 0 = both ways, 1 = client->server, 2 = server->client
*/
-void askcipher(void *frontend, char *ciphername, int cs)
+void askalg(void *frontend, const char *algtype, const char *algname)
{
static const char msg[] =
- "The first %scipher supported by the server is\n"
+ "The first %s supported by the server is\n"
"%s, which is below the configured warning threshold.\n"
"Continue with connection? (y/n) ";
static const char msg_batch[] =
- "The first %scipher supported by the server is\n"
+ "The first %s supported by the server is\n"
"%s, which is below the configured warning threshold.\n"
"Connection abandoned.\n";
static const char abandoned[] = "Connection abandoned.\n";
char line[32];
if (console_batch_mode) {
- fprintf(stderr, msg_batch,
- (cs == 0) ? "" :
- (cs == 1) ? "client-to-server " : "server-to-client ",
- ciphername);
+ fprintf(stderr, msg_batch, algtype, algname);
cleanup_exit(1);
}
- fprintf(stderr, msg,
- (cs == 0) ? "" :
- (cs == 1) ? "client-to-server " : "server-to-client ",
- ciphername);
+ fprintf(stderr, msg, algtype, algname);
fflush(stderr);
{
}
/*
- * Ask whether the selected cipher is acceptable (since it was
+ * Ask whether the selected algorithm is acceptable (since it was
* below the configured 'warn' threshold).
- * cs: 0 = both ways, 1 = client->server, 2 = server->client
*/
-void askcipher(void *frontend, char *ciphername, int cs)
+void askalg(void *frontend, const char *algtype, const char *algname)
{
HANDLE hin;
DWORD savemode, i;
static const char msg[] =
- "The first %scipher supported by the server is\n"
+ "The first %s supported by the server is\n"
"%s, which is below the configured warning threshold.\n"
"Continue with connection? (y/n) ";
static const char msg_batch[] =
- "The first %scipher supported by the server is\n"
+ "The first %s supported by the server is\n"
"%s, which is below the configured warning threshold.\n"
"Connection abandoned.\n";
static const char abandoned[] = "Connection abandoned.\n";
char line[32];
if (console_batch_mode) {
- fprintf(stderr, msg_batch,
- (cs == 0) ? "" :
- (cs == 1) ? "client-to-server " : "server-to-client ",
- ciphername);
+ fprintf(stderr, msg_batch, algtype, algname);
cleanup_exit(1);
}
- fprintf(stderr, msg,
- (cs == 0) ? "" :
- (cs == 1) ? "client-to-server " : "server-to-client ",
- ciphername);
+ fprintf(stderr, msg, algtype, algname);
fflush(stderr);
hin = GetStdHandle(STD_INPUT_HANDLE);
}
/*
- * Ask whether the selected cipher is acceptable (since it was
+ * Ask whether the selected algorithm is acceptable (since it was
* below the configured 'warn' threshold).
- * cs: 0 = both ways, 1 = client->server, 2 = server->client
*/
-void askcipher(void *frontend, char *ciphername, int cs)
+void askalg(void *frontend, const char *algtype, const char *algname)
{
static const char mbtitle[] = "%s Security Alert";
static const char msg[] =
- "The first %.35scipher supported by the server\n"
+ "The first %s supported by the server\n"
"is %.64s, which is below the configured\n"
"warning threshold.\n"
"Do you want to continue with this connection?\n";
char *message, *title;
int mbret;
- message = dupprintf(msg, ((cs == 0) ? "" :
- (cs == 1) ? "client-to-server " :
- "server-to-client "), ciphername);
+ message = dupprintf(msg, algtype, algname);
title = dupprintf(mbtitle, appname);
mbret = MessageBox(NULL, message, title,
MB_ICONWARNING | MB_YESNO);
#define WINHELP_CTX_ssh_protocol "ssh.protocol"
#define WINHELP_CTX_ssh_command "ssh.command"
#define WINHELP_CTX_ssh_compress "ssh.compress"
+#define WINHELP_CTX_ssh_kexlist "ssh.kex.order"
#define WINHELP_CTX_ssh_auth_privkey "ssh.auth.privkey"
#define WINHELP_CTX_ssh_auth_agentfwd "ssh.auth.agentfwd"
#define WINHELP_CTX_ssh_auth_changeuser "ssh.auth.changeuser"
#define WINHELP_CTX_ssh_bugs_hmac2 "ssh.bugs.hmac2"
#define WINHELP_CTX_ssh_bugs_derivekey2 "ssh.bugs.derivekey2"
#define WINHELP_CTX_ssh_bugs_rsapad2 "ssh.bugs.rsapad2"
-#define WINHELP_CTX_ssh_bugs_dhgex2 "ssh.bugs.dhgex2"
#define WINHELP_CTX_ssh_bugs_pksessid2 "ssh.bugs.pksessid2"