From d515e4f1a34b4122195b113ba42ad1c4d2de9085 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 26 Apr 2018 07:18:59 +0100 Subject: [PATCH] Support GSS key exchange, for Kerberos 5 only. This is a heavily edited (by me) version of a patch originally due to Nico Williams and Viktor Dukhovni. Their comments: * Don't delegate credentials when rekeying unless there's a new TGT or the old service ticket is nearly expired. * Check for the above conditions more frequently (every two minutes by default) and rekey when we would delegate credentials. * Do not rekey with very short service ticket lifetimes; some GSSAPI libraries may lose the race to use an almost expired ticket. Adjust the timing of rekey checks to try to avoid this possibility. My further comments: The most interesting thing about this patch to me is that the use of GSS key exchange causes a switch over to a completely different model of what host keys are for. This comes from RFC 4462 section 2.1: the basic idea is that when your session is mostly bidirectionally authenticated by the GSSAPI exchanges happening in initial kex and every rekey, host keys become more or less vestigial, and their remaining purpose is to allow a rekey to happen if the requirements of the SSH protocol demand it at an awkward moment when the GSS credentials are not currently available (e.g. timed out and haven't been renewed yet). As such, there's no need for host keys to be _permanent_ or to be a reliable identifier of a particular host, and RFC 4462 allows for the possibility that they might be purely transient and only for this kind of emergency fallback purpose. Therefore, once PuTTY has done a GSS key exchange, it disconnects itself completely from the permanent host key cache functions in storage.h, and instead switches to a _transient_ host key cache stored in memory with the lifetime of just that SSH session. That cache is populated with keys received from the server as a side effect of GSS kex (via the optional SSH2_MSG_KEXGSS_HOSTKEY message), and used if later in the session we have to fall back to a non-GSS key exchange. However, in practice servers we've tested against do not send a host key in that way, so we also have a fallback method of populating the transient cache by triggering an immediate non-GSS rekey straight after userauth (reusing the code path we also use to turn on OpenSSH delayed encryption without the race condition). --- config.c | 11 +- doc/config.but | 53 +- pgssapi.h | 37 ++ putty.h | 9 + settings.c | 19 +- ssh.c | 1195 +++++++++++++++++++++++++++++++++++++++------ ssh.h | 10 +- sshdh.c | 40 ++ sshgss.h | 21 +- sshgssc.c | 89 +++- sshgssc.h | 1 + unix/uxgss.c | 6 + windows/wingss.c | 119 ++++- windows/winhelp.h | 1 + 14 files changed, 1444 insertions(+), 167 deletions(-) diff --git a/config.c b/config.c index 9116f541..25b6e0ba 100644 --- a/config.c +++ b/config.c @@ -442,7 +442,7 @@ static void kexlist_handler(union control *ctrl, void *dlg, /* (kexlist assumed to contain all algorithms) */ dlg_update_start(ctrl, dlg); dlg_listbox_clear(ctrl, dlg); - for (i = 0; i < KEX_MAX; i++) { + for (i = 0; i < KEX_MAX_CONF; i++) { int k = conf_get_int_int(conf, CONF_ssh_kexlist, i); int j; const char *kstr = NULL; @@ -460,7 +460,7 @@ static void kexlist_handler(union control *ctrl, void *dlg, int i; /* Update array to match the list box. */ - for (i=0; i < KEX_MAX; i++) + for (i=0; i < KEX_MAX_CONF; i++) conf_set_int_int(conf, CONF_ssh_kexlist, i, dlg_listbox_getid(ctrl, dlg, i)); } @@ -2402,7 +2402,7 @@ void setup_config_box(struct controlbox *b, int midsession, c = ctrl_draglist(s, "Algorithm selection policy:", 's', HELPCTX(ssh_kexlist), kexlist_handler, P(NULL)); - c->listbox.height = 5; + c->listbox.height = KEX_MAX_CONF; s = ctrl_getset(b, "Connection/SSH/Kex", "repeat", "Options controlling key re-exchange"); @@ -2412,6 +2412,11 @@ void setup_config_box(struct controlbox *b, int midsession, conf_editbox_handler, I(CONF_ssh_rekey_time), I(-1)); + ctrl_editbox(s, "Minutes between GSS checks (0 for never)", NO_SHORTCUT, 20, + HELPCTX(ssh_kex_repeat), + conf_editbox_handler, + I(CONF_gssapirekey), + I(-1)); ctrl_editbox(s, "Max data before rekey (0 for no limit)", 'x', 20, HELPCTX(ssh_kex_repeat), conf_editbox_handler, diff --git a/doc/config.but b/doc/config.but index 767c1274..1a1e1ac4 100644 --- a/doc/config.but +++ b/doc/config.but @@ -1929,9 +1929,10 @@ PuTTY will prompt for a username at the time you make a connection. In some environments, such as the networks of large organisations implementing \i{single sign-on}, a more sensible default may be to use the name of the user logged in to the local operating system (if any); -this is particularly likely to be useful with \i{GSSAPI} authentication -(see \k{config-ssh-auth-gssapi}). This control allows you to change -the default behaviour. +this is particularly likely to be useful with \i{GSSAPI} key exchange +and user authentication (see \k{config-ssh-auth-gssapi} and +\k{config-ssh-kex}). This control allows you to change the default +behaviour. The current system username is displayed in the dialog as a convenience. It is not saved in the configuration; if a saved session @@ -2552,6 +2553,34 @@ 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}). +\S2{config-ssh-gssapi-kex} GSSAPI-based key exchange + +PuTTY supports a set of key exchange methods that also incorporates +GSSAPI-based authentication. + +PuTTY can only perform the GSSAPI-authenticated key exchange methods +when using Kerberos V5, and not other GSSAPI mechanisms. PuTTY will +attempt to select these methods if it is configured to use GSSAPI +authentication (\k{config-ssh-auth-gssapi}), and if the user running +it has current Kerberos V5 credentials. If both of those are true, +then PuTTY will select the GSSAPI key exchange methods in preference +to any of the ordinary SSH key exchange methods configured in the +preference list. + +The advantage of doing GSSAPI authentication as part of the SSH key +exchange is that the SSH key exchange can be repeated later in the +session, and this allows your Kerberos V5 credentials (which are +typically short-lived) to be automatically re-delegated to the server +when they are refreshed on the client. (This feature is commonly +referred to as \q{cascading credentials}.) + +If your server doesn't support GSSAPI key exchange, it may still +support GSSAPI in the SSH user authentication phase. This will still +let you log in using your Kerberos credentials, but will only allow +you to delegate the credentials that are active at the beginning of +the session; they can't be refreshed automatically later, in a +long-running session. + \S{config-ssh-kex-rekey} \ii{Repeat key exchange} \cfg{winhelp-topic}{ssh.kex.repeat} @@ -2594,6 +2623,14 @@ purposes, rekeys have much the same properties as keepalives. should bear that in mind when deciding whether to turn them off.) Note, however, the the SSH \e{server} can still initiate rekeys. +\b \q{Minutes between GSSAPI cache checks}, if you're using GSSAPI key +exchange, specifies how often the GSSAPI credential cache is checked +to see whether new tickets are available for delegation, or current +ones are near expiration. If forwarding of GSSAPI credentials is +enabled, PuTTY will try to rekey as necessary to keep the delegated +credentials from expiring. Frequent checks are recommended; rekeying +only happens when needed. + \b \q{Max data before rekey} specifies the amount of data (in bytes) that is permitted to flow in either direction before a rekey is initiated. If this is set to zero, PuTTY will not rekey due to @@ -2947,7 +2984,15 @@ 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 to implement \i{passwordless login}. -GSSAPI is only available in the SSH-2 protocol. +GSSAPI authentication is only available in the SSH-2 protocol. + +PuTTY supports two forms of GSSAPI-based authentication. In one of +them, the SSH key exchange happens in the normal way, and GSSAPI is +only involved in authenticating the user. In the other, GSSAPI-based +authentication is combined with the key exchange phase, and the SSH +authentication step has nothing left to do. If you enable GSSAPI +authentication, PuTTY will attempt both of these methods, and use +whichever the server supports. The topmost control on the GSSAPI subpanel is the checkbox labelled \q{Attempt GSSAPI authentication}. If this is disabled, GSSAPI will diff --git a/pgssapi.h b/pgssapi.h index f6370c2d..fd7a796d 100644 --- a/pgssapi.h +++ b/pgssapi.h @@ -58,6 +58,7 @@ typedef void * gss_name_t; typedef void * gss_cred_id_t; typedef OM_uint32 gss_qop_t; +typedef int gss_cred_usage_t; /* Flag bits for context-level services. */ @@ -76,6 +77,13 @@ typedef OM_uint32 gss_qop_t; #define GSS_C_INITIATE 1 #define GSS_C_ACCEPT 2 +/*- + * RFC 2744 Page 86 + * Expiration time of 2^32-1 seconds means infinite lifetime for a + * credential or security context + */ +#define GSS_C_INDEFINITE 0xfffffffful + /* Status code types for gss_display_status */ #define GSS_C_GSS_CODE 1 #define GSS_C_MECH_CODE 2 @@ -256,6 +264,13 @@ typedef OM_uint32 (GSS_CC *t_gss_get_mic) const gss_buffer_t /*message_buffer*/, gss_buffer_t /*msg_token*/); +typedef OM_uint32 (GSS_CC *t_gss_verify_mic) + (OM_uint32 * /*minor_status*/, + const gss_ctx_id_t /*context_handle*/, + const gss_buffer_t /*message_buffer*/, + const gss_buffer_t /*msg_token*/, + gss_qop_t * /*qop_state*/); + typedef OM_uint32 (GSS_CC *t_gss_display_status) (OM_uint32 * /*minor_status*/, OM_uint32 /*status_value*/, @@ -280,15 +295,37 @@ typedef OM_uint32 (GSS_CC *t_gss_release_buffer) (OM_uint32 * /*minor_status*/, gss_buffer_t /*buffer*/); +typedef OM_uint32 (GSS_CC *t_gss_acquire_cred) + (OM_uint32 * /*minor_status*/, + const gss_name_t /*desired_name*/, + OM_uint32 /*time_req*/, + const gss_OID_set /*desired_mechs*/, + gss_cred_usage_t /*cred_usage*/, + gss_cred_id_t * /*output_cred_handle*/, + gss_OID_set * /*actual_mechs*/, + OM_uint32 * /*time_rec*/); + +typedef OM_uint32 (GSS_CC *t_gss_inquire_cred_by_mech) + (OM_uint32 * /*minor_status*/, + const gss_cred_id_t /*cred_handle*/, + const gss_OID /*mech_type*/, + gss_name_t * /*name*/, + OM_uint32 * /*initiator_lifetime*/, + OM_uint32 * /*acceptor_lifetime*/, + gss_cred_usage_t * /*cred_usage*/); + struct gssapi_functions { t_gss_delete_sec_context delete_sec_context; t_gss_display_status display_status; t_gss_get_mic get_mic; + t_gss_verify_mic verify_mic; t_gss_import_name import_name; t_gss_init_sec_context init_sec_context; t_gss_release_buffer release_buffer; t_gss_release_cred release_cred; t_gss_release_name release_name; + t_gss_acquire_cred acquire_cred; + t_gss_inquire_cred_by_mech inquire_cred_by_mech; }; #endif /* NO_GSSAPI */ diff --git a/putty.h b/putty.h index 3f2c3f1b..a7cee555 100644 --- a/putty.h +++ b/putty.h @@ -271,6 +271,14 @@ enum { KEX_DHGEX, KEX_RSA, KEX_ECDH, + /* + * KEX_MAX_CONF is a boundary between statically and dynamically configured + * KEXes, without creating a gap in the numbering, allowing easy addition + * of vaues on either side + */ + KEX_MAX_CONF, KEX_DUMMY = KEX_MAX_CONF-1, + /* Kexes from here to KEX_MAX are not explicitly configurable */ + KEX_GSS_SHA1_K5, KEX_MAX }; @@ -796,6 +804,7 @@ void cleanup_exit(int); X(INT, NONE, try_ki_auth) \ X(INT, NONE, try_gssapi_auth) /* attempt gssapi auth */ \ X(INT, NONE, gssapifwd) /* forward tgt via gss */ \ + X(INT, NONE, gssapirekey) /* KEXGSS refresh interval (mins) */ \ X(INT, INT, ssh_gsslist) /* preference order for local GSS libs */ \ X(FILENAME, NONE, ssh_gss_custom) \ X(INT, NONE, ssh_subsys) /* run a subsystem rather than a command */ \ diff --git a/settings.c b/settings.c index 33a0a552..a6b59e28 100644 --- a/settings.c +++ b/settings.c @@ -7,6 +7,11 @@ #include #include "putty.h" #include "storage.h" +#ifndef NO_GSSAPI +#include "sshgssc.h" +#include "sshgss.h" +#endif + /* The cipher order given here is the default order. */ static const struct keyvalwhere ciphernames[] = { @@ -566,9 +571,10 @@ void save_open_settings(void *sesskey, Conf *conf) write_setting_i(sesskey, "GssapiFwd", conf_get_int(conf, CONF_gssapifwd)); write_setting_i(sesskey, "ChangeUsername", conf_get_int(conf, CONF_change_username)); wprefs(sesskey, "Cipher", ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist); - wprefs(sesskey, "KEX", kexnames, KEX_MAX, conf, CONF_ssh_kexlist); + wprefs(sesskey, "KEX", kexnames, KEX_MAX_CONF, conf, CONF_ssh_kexlist); wprefs(sesskey, "HostKey", hknames, HK_MAX, conf, CONF_ssh_hklist); write_setting_i(sesskey, "RekeyTime", conf_get_int(conf, CONF_ssh_rekey_time)); + write_setting_i(sesskey, "GssapiRekey", conf_get_int(conf, CONF_gssapirekey)); write_setting_s(sesskey, "RekeyBytes", conf_get_str(conf, CONF_ssh_rekey_data)); write_setting_i(sesskey, "SshNoAuth", conf_get_int(conf, CONF_ssh_no_userauth)); write_setting_i(sesskey, "SshBanner", conf_get_int(conf, CONF_ssh_show_banner)); @@ -910,10 +916,10 @@ void load_open_settings(void *sesskey, Conf *conf) * a server which offered it then choked, but we never got * a server version string or any other reports. */ const char *default_kexes, - *normal_default = "ecdh,dh-gex-sha1,dh-group14-sha1,rsa," - "WARN,dh-group1-sha1", - *bugdhgex2_default = "ecdh,dh-group14-sha1,rsa," - "WARN,dh-group1-sha1,dh-gex-sha1"; + *normal_default = "gss-sha1-krb5,ecdh,dh-gex-sha1," + "dh-group14-sha1,rsa,WARN,dh-group1-sha1", + *bugdhgex2_default = "gss-sha1-krb5,ecdh,dh-group14-sha1," + "rsa,WARN,dh-group1-sha1,dh-gex-sha1"; char *raw; i = 2 - gppi_raw(sesskey, "BugDHGEx2", 0); if (i == FORCE_ON) @@ -940,12 +946,13 @@ void load_open_settings(void *sesskey, Conf *conf) sfree(raw); raw = dupstr(normal_default); } - gprefs_from_str(raw, kexnames, KEX_MAX, conf, CONF_ssh_kexlist); + gprefs_from_str(raw, kexnames, KEX_MAX_CONF, conf, CONF_ssh_kexlist); sfree(raw); } gprefs(sesskey, "HostKey", "ed25519,ecdsa,rsa,dsa,WARN", hknames, HK_MAX, conf, CONF_ssh_hklist); gppi(sesskey, "RekeyTime", 60, conf, CONF_ssh_rekey_time); + gppi(sesskey, "GssapiRekey", 2, conf, CONF_gssapirekey); gpps(sesskey, "RekeyBytes", "1G", conf, CONF_ssh_rekey_data); { /* SSH-2 only by default */ diff --git a/ssh.c b/ssh.c index 15be3175..2ba3a17c 100644 --- a/ssh.c +++ b/ssh.c @@ -17,6 +17,12 @@ #ifndef NO_GSSAPI #include "sshgssc.h" #include "sshgss.h" +#define GSS_DEF_REKEY_MINS 2 /* Default minutes between GSS cache checks */ +#define MIN_CTXT_LIFETIME 5 /* Avoid rekey with short lifetime (seconds) */ +#define GSS_KEX_CAPABLE (1<<0) /* Can do GSS KEX */ +#define GSS_CRED_UPDATED (1<<1) /* Cred updated since previous delegation */ +#define GSS_CTXT_EXPIRES (1<<2) /* Context expires before next timer */ +#define GSS_CTXT_MAYFAIL (1<<3) /* Context may expire during handshake */ #endif #ifndef FALSE @@ -35,6 +41,7 @@ typedef enum { SSH2_PKTCTX_DHGROUP, SSH2_PKTCTX_DHGEX, SSH2_PKTCTX_ECDHKEX, + SSH2_PKTCTX_GSSKEX, SSH2_PKTCTX_RSAKEX } Pkt_KCtx; typedef enum { @@ -274,6 +281,13 @@ static const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, translatek(SSH2_MSG_KEXRSA_DONE, SSH2_PKTCTX_RSAKEX); translatek(SSH2_MSG_KEX_ECDH_INIT, SSH2_PKTCTX_ECDHKEX); translatek(SSH2_MSG_KEX_ECDH_REPLY, SSH2_PKTCTX_ECDHKEX); + translatek(SSH2_MSG_KEXGSS_INIT, SSH2_PKTCTX_GSSKEX); + translatek(SSH2_MSG_KEXGSS_CONTINUE, SSH2_PKTCTX_GSSKEX); + translatek(SSH2_MSG_KEXGSS_COMPLETE, SSH2_PKTCTX_GSSKEX); + translatek(SSH2_MSG_KEXGSS_HOSTKEY, SSH2_PKTCTX_GSSKEX); + translatek(SSH2_MSG_KEXGSS_ERROR, SSH2_PKTCTX_GSSKEX); + translatek(SSH2_MSG_KEXGSS_GROUPREQ, SSH2_PKTCTX_GSSKEX); + translatek(SSH2_MSG_KEXGSS_GROUP, SSH2_PKTCTX_GSSKEX); translate(SSH2_MSG_USERAUTH_REQUEST); translate(SSH2_MSG_USERAUTH_FAILURE); translate(SSH2_MSG_USERAUTH_SUCCESS); @@ -731,6 +745,11 @@ static int ssh2_pkt_getbool(struct Packet *pkt); static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length); static void ssh2_timer(void *ctx, unsigned long now); static int ssh2_timer_update(Ssh ssh, unsigned long rekey_time); +#ifndef NO_GSSAPI +static void ssh2_gss_update(Ssh ssh); +static struct Packet *ssh2_gss_authpacket(Ssh ssh, Ssh_gss_ctx gss_ctx, + const char *authtype); +#endif static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, struct Packet *pktin); static void ssh2_msg_unexpected(Ssh ssh, struct Packet *pktin); @@ -971,10 +990,22 @@ struct ssh_tag { #ifndef NO_GSSAPI /* - * GSSAPI libraries for this session. + * GSSAPI libraries for this session. We need them at key exchange + * and userauth time. + * + * And the gss_ctx we setup at initial key exchange will be used + * during gssapi-keyex userauth time as well. */ struct ssh_gss_liblist *gsslibs; + struct ssh_gss_library *gsslib; + int gss_status; + time_t gss_cred_expiry; /* Re-delegate if newer */ + unsigned long gss_ctxt_lifetime; /* Re-delegate when short */ + Ssh_gss_name gss_srv_name; /* Cached for KEXGSS */ + Ssh_gss_ctx gss_ctx; /* Saved for gssapi-keyex */ + tree234 *transient_hostkey_cache; #endif + int gss_kex_used; /* outside ifdef; always FALSE if NO_GSSAPI */ /* * The last list returned from get_specials. @@ -6342,6 +6373,123 @@ static struct kexinit_algorithm *ssh2_kexinit_addalg(struct kexinit_algorithm return NULL; } +#ifndef NO_GSSAPI +/* + * Data structure managing host keys in sessions based on GSSAPI KEX. + * + * In a session we started with a GSSAPI key exchange, the concept of + * 'host key' has completely different lifetime and security semantics + * from the usual ones. Per RFC 4462 section 2.1, we assume that any + * host key delivered to us in the course of a GSSAPI key exchange is + * _solely_ there to use as a transient fallback within the same + * session, if at the time of a subsequent rekey the GSS credentials + * are temporarily invalid and so a non-GSS KEX method has to be used. + * + * In particular, in a GSS-based SSH deployment, host keys may not + * even _be_ persistent identities for the server; it would be + * legitimate for a server to generate a fresh one routinely if it + * wanted to, like SSH-1 server keys. + * + * So, in this mode, we never touch the persistent host key cache at + * all, either to check keys against it _or_ to store keys in it. + * Instead, we maintain an in-memory cache of host keys that have been + * mentioned in GSS key exchanges within this particular session, and + * we permit precisely those host keys in non-GSS rekeys. + */ +struct ssh_transient_hostkey_cache_entry { + const struct ssh_signkey *alg; + unsigned char *pub_blob; + int pub_len; +}; + +static int ssh_transient_hostkey_cache_cmp(void *av, void *bv) +{ + const struct ssh_transient_hostkey_cache_entry + *a = (const struct ssh_transient_hostkey_cache_entry *)av, + *b = (const struct ssh_transient_hostkey_cache_entry *)bv; + return strcmp(a->alg->name, b->alg->name); +} + +static int ssh_transient_hostkey_cache_find(void *av, void *bv) +{ + const struct ssh_signkey *aalg = (const struct ssh_signkey *)av; + const struct ssh_transient_hostkey_cache_entry + *b = (const struct ssh_transient_hostkey_cache_entry *)bv; + return strcmp(aalg->name, b->alg->name); +} + +static void ssh_init_transient_hostkey_store(Ssh ssh) +{ + ssh->transient_hostkey_cache = + newtree234(ssh_transient_hostkey_cache_cmp); +} + +static void ssh_cleanup_transient_hostkey_store(Ssh ssh) +{ + struct ssh_transient_hostkey_cache_entry *ent; + while ((ent = delpos234(ssh->transient_hostkey_cache, 0)) != NULL) { + sfree(ent->pub_blob); + sfree(ent); + } + freetree234(ssh->transient_hostkey_cache); +} + +static void ssh_store_transient_hostkey( + Ssh ssh, const struct ssh_signkey *alg, void *key) +{ + struct ssh_transient_hostkey_cache_entry *ent, *retd; + + if ((ent = find234(ssh->transient_hostkey_cache, (void *)alg, + ssh_transient_hostkey_cache_find)) != NULL) { + sfree(ent->pub_blob); + sfree(ent); + } + + ent = snew(struct ssh_transient_hostkey_cache_entry); + ent->alg = alg; + ent->pub_blob = alg->public_blob(key, &ent->pub_len); + retd = add234(ssh->transient_hostkey_cache, ent); + assert(retd == ent); +} + +static int ssh_verify_transient_hostkey( + Ssh ssh, const struct ssh_signkey *alg, void *key) +{ + struct ssh_transient_hostkey_cache_entry *ent; + int toret = FALSE; + + if ((ent = find234(ssh->transient_hostkey_cache, (void *)alg, + ssh_transient_hostkey_cache_find)) != NULL) { + int this_len; + unsigned char *this_blob = alg->public_blob(key, &this_len); + + if (this_len == ent->pub_len && + !memcmp(this_blob, ent->pub_blob, this_len)) + toret = TRUE; + + sfree(this_blob); + } + + return toret; +} + +static int ssh_have_transient_hostkey(Ssh ssh, const struct ssh_signkey *alg) +{ + struct ssh_transient_hostkey_cache_entry *ent = + find234(ssh->transient_hostkey_cache, (void *)alg, + ssh_transient_hostkey_cache_find); + return ent != NULL; +} + +static int ssh_have_any_transient_hostkey(Ssh ssh) +{ + return count234(ssh->transient_hostkey_cache) > 0; +} + +#endif /* NO_GSSAPI */ + +#define GSS_UPDATE_REKEY_REASON "GSS credentials updated" + /* * Handle the SSH-2 transport layer. */ @@ -6383,6 +6531,9 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, void *eckey; /* for ECDH kex */ unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN]; int n_preferred_kex; + int can_gssapi_keyex; + int need_gss_transient_hostkey; + int warned_about_no_gss_transient_hostkey; const struct ssh_kexes *preferred_kex[KEX_MAX]; int n_preferred_hk; int preferred_hk[HK_MAX]; @@ -6397,6 +6548,17 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, int guessok; int ignorepkt; struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST]; +#ifndef NO_GSSAPI + Ssh_gss_buf gss_buf; + Ssh_gss_buf gss_rcvtok, gss_sndtok; + Ssh_gss_stat gss_stat; + Ssh_gss_ctx gss_ctx; + Ssh_gss_buf mic; + int init_token_sent; + int complete_rcvd; + int gss_delegate; + time_t gss_cred_expiry; +#endif }; crState(do_ssh2_transport_state); @@ -6412,6 +6574,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, s->got_session_id = s->activated_authconn = FALSE; s->userauth_succeeded = FALSE; s->pending_compression = FALSE; + s->need_gss_transient_hostkey = FALSE; + s->warned_about_no_gss_transient_hostkey = FALSE; /* * Be prepared to work around the buggy MAC problem. @@ -6422,6 +6586,52 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, s->maclist = macs, s->nmacs = lenof(macs); begin_key_exchange: + +#ifndef NO_GSSAPI + if (s->need_gss_transient_hostkey) { + /* + * This flag indicates a special case in which we must not do + * GSS key exchange even if we could. (See comments below, + * where the flag was set on the previous key exchange.) + */ + s->can_gssapi_keyex = FALSE; + } else { + /* + * We always check if we have GSS creds before we come up with + * the kex algorithm list, otherwise future rekeys will fail + * when creds expire. To make this so, this code section must + * follow the begin_key_exchange label above, otherwise this + * section would execute just once per-connection. + * + * Update GSS state unless the reason we're here is that a + * timer just checked the GSS state and decided that we should + * rekey to update delegated credentials. In that case, the + * state is "fresh". + */ + if (!vin || strcmp(vin, GSS_UPDATE_REKEY_REASON) != 0) + ssh2_gss_update(ssh); + + /* Do GSSAPI KEX when capable */ + s->can_gssapi_keyex = ssh->gss_status & GSS_KEX_CAPABLE; + + /* + * But not when failure is likely. [ GSS implementations may + * attempt (and fail) to use a ticket that is almost expired + * when retrieved from the ccache that actually expires by the + * time the server receives it. ] + * + * Note: The first time always try KEXGSS if we can, failures + * will be very rare, and disabling the initial GSS KEX is + * worse. Some day GSS libraries will ignore cached tickets + * whose lifetime is critically short, and will instead use + * fresh ones. + */ + if (!s->got_session_id && (ssh->gss_status & GSS_CTXT_MAYFAIL) != 0) + s->can_gssapi_keyex = 0; + s->gss_delegate = conf_get_int(ssh->conf, CONF_gssapifwd); + } +#endif + ssh->pkt_kctx = SSH2_PKTCTX_NOKEX; { int i, j, k, warn; @@ -6431,7 +6641,9 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, * Set up the preferred key exchange. (NULL => warn below here) */ s->n_preferred_kex = 0; - for (i = 0; i < KEX_MAX; i++) { + if (s->can_gssapi_keyex) + s->preferred_kex[s->n_preferred_kex++] = &ssh_gssk5_sha1_kex; + for (i = 0; i < KEX_MAX_CONF; i++) { switch (conf_get_int_int(ssh->conf, CONF_ssh_kexlist, i)) { case KEX_DHGEX: s->preferred_kex[s->n_preferred_kex++] = @@ -6590,6 +6802,36 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, alg->u.hk.warn = warn; } } +#ifndef NO_GSSAPI + } else if (ssh->gss_kex_used && !s->need_gss_transient_hostkey) { + /* + * If we've previously done a GSSAPI KEX, then we list + * precisely the algorithms for which a previous GSS key + * exchange has delivered us a host key, because we expect + * one of exactly those keys to be used in any subsequent + * non-GSS-based rekey. + * + * An exception is if this is the key exchange we + * triggered for the purposes of populating that cache - + * in which case the cache will currently be empty, which + * isn't helpful! + */ + 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 (ssh_have_transient_hostkey(ssh, hostkey_algs[j].alg)) { + 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; + } + } + } +#endif } else { /* * In subsequent key exchanges, we list only the kex @@ -6604,6 +6846,10 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, alg->u.hk.hostkey = ssh->hostkey; alg->u.hk.warn = FALSE; } + if (s->can_gssapi_keyex) { + alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY], "null"); + alg->u.hk.hostkey = NULL; + } /* List encryption algorithms (client->server then server->client). */ for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) { warn = FALSE; @@ -6768,6 +7014,15 @@ 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) { + /* + * Ignore an unexpected/inappropriate offer of "null", + * we offer "null" when we're willing to use GSS KEX, + * but it is only acceptable when GSSKEX is actually + * selected. + */ + if (alg->u.hk.hostkey == NULL && + ssh->kex->main_type != KEXTYPE_GSS) + continue; ssh->hostkey = alg->u.hk.hostkey; s->warn_hk = alg->u.hk.warn; } else if (i == KEXLIST_CSCIPHER) { @@ -6798,7 +7053,9 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, crStopV; matched:; - if (i == KEXLIST_HOSTKEY) { + if (i == KEXLIST_HOSTKEY && + !ssh->gss_kex_used && + ssh->kex->main_type != KEXTYPE_GSS) { int j; /* @@ -7209,7 +7466,266 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, } ssh_ecdhkex_freekey(s->eckey); +#ifndef NO_GSSAPI + } else if (ssh->kex->main_type == KEXTYPE_GSS) { + int len; + char *data; + + ssh->pkt_kctx = SSH2_PKTCTX_GSSKEX; + s->init_token_sent = 0; + s->complete_rcvd = 0; + s->hostkeydata = NULL; + s->hostkeylen = 0; + s->hkey = NULL; + s->fingerprint = NULL; + s->keystr = NULL; + + /* + * Work out the number of bits of key we will need from the + * key exchange. We start with the maximum key length of + * either cipher... + * + * This is rote from the KEXTYPE_DH section above. + */ + { + int csbits, scbits; + + csbits = s->cscipher_tobe->real_keybits; + scbits = s->sccipher_tobe->real_keybits; + s->nbits = (csbits > scbits ? csbits : scbits); + } + /* The keys only have hlen-bit entropy, since they're based on + * a hash. So cap the key size at hlen bits. */ + if (s->nbits > ssh->kex->hash->hlen * 8) + s->nbits = ssh->kex->hash->hlen * 8; + + if (dh_is_gex(ssh->kex)) { + /* + * Work out how big a DH group we will need to allow that + * much data. + */ + s->pbits = 512 << ((s->nbits - 1) / 64); + logeventf(ssh, "Doing GSSAPI (with Kerberos V5) Diffie-Hellman " + "group exchange, with minimum %d bits", s->pbits); + s->pktout = ssh2_pkt_init(SSH2_MSG_KEXGSS_GROUPREQ); + ssh2_pkt_adduint32(s->pktout, s->pbits); /* min */ + ssh2_pkt_adduint32(s->pktout, s->pbits); /* preferred */ + ssh2_pkt_adduint32(s->pktout, s->pbits * 2); /* max */ + ssh2_pkt_send_noqueue(ssh, s->pktout); + + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_KEXGSS_GROUP) { + bombout(("expected key exchange group packet from server")); + crStopV; + } + s->p = ssh2_pkt_getmp(pktin); + s->g = ssh2_pkt_getmp(pktin); + if (!s->p || !s->g) { + bombout(("unable to read mp-ints from incoming group packet")); + crStopV; + } + ssh->kex_ctx = dh_setup_gex(s->p, s->g); + } else { + ssh->kex_ctx = dh_setup_group(ssh->kex); + logeventf(ssh, "Using GSSAPI (with Kerberos V5) Diffie-Hellman with standard group \"%s\"", + ssh->kex->groupname); + } + + logeventf(ssh, "Doing GSSAPI (with Kerberos V5) Diffie-Hellman key exchange with hash %s", + ssh->kex->hash->text_name); + /* Now generate e for Diffie-Hellman. */ + set_busy_status(ssh->frontend, BUSY_CPU); /* this can take a while */ + s->e = dh_create_e(ssh->kex_ctx, s->nbits * 2); + + if (ssh->gsslib->gsslogmsg) + logevent(ssh->gsslib->gsslogmsg); + + /* initial tokens are empty */ + SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); + SSH_GSS_CLEAR_BUF(&s->gss_sndtok); + SSH_GSS_CLEAR_BUF(&s->mic); + s->gss_stat = ssh->gsslib->acquire_cred(ssh->gsslib, &s->gss_ctx, + &s->gss_cred_expiry); + if (s->gss_stat != SSH_GSS_OK) { + bombout(("GSSAPI key exchange failed to initialize")); + crStopV; + } + + /* now enter the loop */ + assert(ssh->gss_srv_name); + do { + /* + * When acquire_cred yields no useful expiration, go with the + * service ticket expiration. + */ + s->gss_stat = ssh->gsslib->init_sec_context + (ssh->gsslib, + &s->gss_ctx, + ssh->gss_srv_name, + s->gss_delegate, + &s->gss_rcvtok, + &s->gss_sndtok, + (s->gss_cred_expiry == GSS_NO_EXPIRATION ? + &s->gss_cred_expiry : NULL), + NULL); + SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); + + if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd) + break; /* MIC is verified after the loop */ + + if (s->gss_stat != SSH_GSS_S_COMPLETE && + s->gss_stat != SSH_GSS_S_CONTINUE_NEEDED) { + if (ssh->gsslib->display_status(ssh->gsslib, s->gss_ctx, + &s->gss_buf) == SSH_GSS_OK) { + bombout(("GSSAPI key exchange failed to initialize" + " context: %s", (char *)s->gss_buf.value)); + sfree(s->gss_buf.value); + crStopV; + } else { + bombout(("GSSAPI key exchange failed to initialize" + " context")); + crStopV; + } + } + assert(s->gss_stat == SSH_GSS_S_COMPLETE || + s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED); + + if (!s->init_token_sent) { + s->init_token_sent = 1; + s->pktout = ssh2_pkt_init(SSH2_MSG_KEXGSS_INIT); + if (s->gss_sndtok.length == 0) { + bombout(("GSSAPI key exchange failed:" + " no initial context token")); + crStopV; + } + ssh_pkt_addstring_start(s->pktout); + ssh_pkt_addstring_data(s->pktout, + s->gss_sndtok.value, + s->gss_sndtok.length); + ssh2_pkt_addmp(s->pktout, s->e); + ssh2_pkt_send_noqueue(ssh, s->pktout); + ssh->gsslib->free_tok(ssh->gsslib, &s->gss_sndtok); + logevent("GSSAPI key exchange initialised"); + } else if (s->gss_sndtok.length != 0) { + s->pktout = ssh2_pkt_init(SSH2_MSG_KEXGSS_CONTINUE); + ssh_pkt_addstring_start(s->pktout); + ssh_pkt_addstring_data(s->pktout, + s->gss_sndtok.value, + s->gss_sndtok.length); + ssh2_pkt_send_noqueue(ssh, s->pktout); + ssh->gsslib->free_tok(ssh->gsslib, &s->gss_sndtok); + } + + if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd) + break; + + wait_for_gss_token: + crWaitUntilV(pktin); + switch (pktin->type) { + case SSH2_MSG_KEXGSS_CONTINUE: + ssh_pkt_getstring(pktin, &data, &len); + s->gss_rcvtok.value = data; + s->gss_rcvtok.length = len; + continue; + case SSH2_MSG_KEXGSS_COMPLETE: + s->complete_rcvd = 1; + s->f = ssh2_pkt_getmp(pktin); + ssh_pkt_getstring(pktin, &data, &len); + s->mic.value = data; + s->mic.length = len; + /* Save expiration time of cred when delegating */ + if (s->gss_delegate && s->gss_cred_expiry != GSS_NO_EXPIRATION) + ssh->gss_cred_expiry = s->gss_cred_expiry; + /* If there's a final token we loop to consume it */ + if (ssh2_pkt_getbool(pktin)) { + ssh_pkt_getstring(pktin, &data, &len); + s->gss_rcvtok.value = data; + s->gss_rcvtok.length = len; + continue; + } + break; + case SSH2_MSG_KEXGSS_HOSTKEY: + ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); + if (ssh->hostkey) { + s->hkey = ssh->hostkey->newkey(ssh->hostkey, + s->hostkeydata, + s->hostkeylen); + hash_string(ssh->kex->hash, ssh->exhash, + s->hostkeydata, s->hostkeylen); + } + /* + * Can't loop as we have no token to pass to + * init_sec_context. + */ + goto wait_for_gss_token; + case SSH2_MSG_KEXGSS_ERROR: + /* + * We have no use for the server's major and minor + * status. The minor status is really only + * meaningful to the server, and with luck the major + * status means something to us (but not really all + * that much). The string is more meaningful, and + * hopefully the server sends any error tokens, as + * that will produce the most useful information for + * us. + */ + ssh_pkt_getuint32(pktin); /* server's major status */ + ssh_pkt_getuint32(pktin); /* server's minor status */ + ssh_pkt_getstring(pktin, &data, &len); + logeventf(ssh, "GSSAPI key exchange failed; " + "server's message: %.*s", len, data); + /* Language tag, but we have no use for it */ + ssh_pkt_getstring(pktin, &data, &len); + /* + * Wait for an error token, if there is one, or the + * server's disconnect. The error token, if there + * is one, must follow the SSH2_MSG_KEXGSS_ERROR + * message, per the RFC. + */ + goto wait_for_gss_token; + default: + bombout(("unexpected message type during gss kex")); + crStopV; + break; + } + } while (s->gss_rcvtok.length || + s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED || + !s->complete_rcvd); + + s->K = dh_find_K(ssh->kex_ctx, s->f); + + /* We assume everything from now on will be quick, and it might + * involve user interaction. */ + set_busy_status(ssh->frontend, BUSY_NOT); + + if (!s->hkey) + hash_string(ssh->kex->hash, ssh->exhash, NULL, 0); + if (dh_is_gex(ssh->kex)) { + /* min, preferred, max */ + hash_uint32(ssh->kex->hash, ssh->exhash, s->pbits); + hash_uint32(ssh->kex->hash, ssh->exhash, s->pbits); + hash_uint32(ssh->kex->hash, ssh->exhash, s->pbits * 2); + + hash_mpint(ssh->kex->hash, ssh->exhash, s->p); + hash_mpint(ssh->kex->hash, ssh->exhash, s->g); + } + hash_mpint(ssh->kex->hash, ssh->exhash, s->e); + hash_mpint(ssh->kex->hash, ssh->exhash, s->f); + + /* + * MIC verification is done below, after we compute the hash + * used as the MIC input. + */ + + dh_cleanup(ssh->kex_ctx); + freebn(s->f); + if (dh_is_gex(ssh->kex)) { + freebn(s->g); + freebn(s->p); + } +#endif } else { + assert(ssh->kex->main_type == KEXTYPE_RSA); logeventf(ssh, "Doing RSA key exchange with hash %s", ssh->kex->hash->text_name); ssh->pkt_kctx = SSH2_PKTCTX_RSAKEX; @@ -7328,6 +7844,50 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, assert(ssh->kex->hash->hlen <= sizeof(s->exchange_hash)); ssh->kex->hash->final(ssh->exhash, s->exchange_hash); +#ifndef NO_GSSAPI + if (ssh->kex->main_type == KEXTYPE_GSS) { + Ssh_gss_buf gss_buf; + SSH_GSS_CLEAR_BUF(&s->gss_buf); + + gss_buf.value = s->exchange_hash; + gss_buf.length = ssh->kex->hash->hlen; + s->gss_stat = ssh->gsslib->verify_mic(ssh->gsslib, s->gss_ctx, &gss_buf, &s->mic); + if (s->gss_stat != SSH_GSS_OK) { + if (ssh->gsslib->display_status(ssh->gsslib, s->gss_ctx, + &s->gss_buf) == SSH_GSS_OK) { + bombout(("GSSAPI Key Exchange MIC was not valid: %s", + (char *)s->gss_buf.value)); + sfree(s->gss_buf.value); + } else { + bombout(("GSSAPI Key Exchange MIC was not valid")); + } + crStopV; + } + + ssh->gss_kex_used = TRUE; + + /*- + * If this the first KEX, save the GSS context for "gssapi-keyex" + * authentication. + * + * http://tools.ietf.org/html/rfc4462#section-4 + * + * This method may be used only if the initial key exchange was + * performed using a GSS-API-based key exchange method defined in + * accordance with Section 2. The GSS-API context used with this + * method is always that established during an initial GSS-API-based + * key exchange. Any context established during key exchange for the + * purpose of rekeying MUST NOT be used with this method. + */ + if (!s->got_session_id) { + ssh->gss_ctx = s->gss_ctx; + } else { + ssh->gsslib->release_cred(ssh->gsslib, &s->gss_ctx); + } + logeventf(ssh, "GSSAPI Key Exchange complete!"); + } +#endif + ssh->kex_ctx = NULL; #if 0 @@ -7335,21 +7895,118 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, dmemdump(s->exchange_hash, ssh->kex->hash->hlen); #endif - if (!s->hkey) { - bombout(("Server's host key is invalid")); - crStopV; - } + /* In GSS keyex there's no hostkey signature to verify */ + if (ssh->kex->main_type != KEXTYPE_GSS) { + if (!s->hkey) { + bombout(("Server's host key is invalid")); + crStopV; + } - if (!ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen, - (char *)s->exchange_hash, - ssh->kex->hash->hlen)) { + if (!ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen, + (char *)s->exchange_hash, + ssh->kex->hash->hlen)) { #ifndef FUZZING - bombout(("Server's host key did not match the signature supplied")); - crStopV; + bombout(("Server's host key did not match the signature " + "supplied")); + crStopV; #endif + } } - s->keystr = ssh->hostkey->fmtkey(s->hkey); + s->keystr = (ssh->hostkey && s->hkey ? + ssh->hostkey->fmtkey(s->hkey) : NULL); +#ifndef NO_GSSAPI + if (ssh->gss_kex_used) { + /* + * In a GSS-based session, check the host key (if any) against + * the transient host key cache. See comment above, at the + * definition of ssh_transient_hostkey_cache_entry. + */ + if (ssh->kex->main_type == KEXTYPE_GSS) { + + /* + * We've just done a GSS key exchange. If it gave us a + * host key, store it. + */ + if (s->hkey) { + s->fingerprint = ssh2_fingerprint(ssh->hostkey, s->hkey); + logevent("GSS kex provided fallback host key:"); + logevent(s->fingerprint); + sfree(s->fingerprint); + s->fingerprint = NULL; + ssh_store_transient_hostkey(ssh, ssh->hostkey, s->hkey); + } else if (!ssh_have_any_transient_hostkey(ssh)) { + /* + * But if it didn't, then we currently have no + * fallback host key to use in subsequent non-GSS + * rekeys. So we should immediately trigger a non-GSS + * rekey of our own, to set one up, before the session + * keys have been used for anything else. + * + * This is similar to the cross-certification done at + * user request in the permanent host key cache, but + * here we do it automatically, once, at session + * startup, and only add the key to the transient + * cache. + */ + if (ssh->hostkey) { + s->need_gss_transient_hostkey = TRUE; + } else { + /* + * If we negotiated the "null" host key algorithm + * in the key exchange, that's an indication that + * no host key at all is available from the server + * (both because we listed "null" last, and + * because RFC 4462 section 5 says that a server + * MUST NOT offer "null" as a host key algorithm + * unless that is the only algorithm it provides + * at all). + * + * In that case we actually _can't_ perform a + * non-GSSAPI key exchange, so it's pointless to + * attempt one proactively. This is also likely to + * cause trouble later if a rekey is required at a + * moment whne GSS credentials are not available, + * but someone setting up a server in this + * configuration presumably accepts that as a + * consequence. + */ + if (!s->warned_about_no_gss_transient_hostkey) { + logevent("No fallback host key available"); + s->warned_about_no_gss_transient_hostkey = TRUE; + } + } + } + } else { + /* + * We've just done a fallback key exchange, so make + * sure the host key it used is in the cache of keys + * we previously received in GSS kexes. + * + * An exception is if this was the non-GSS key exchange we + * triggered on purpose to populate the transient cache. + */ + assert(s->hkey); /* only KEXTYPE_GSS lets this be null */ + s->fingerprint = ssh2_fingerprint(ssh->hostkey, s->hkey); + + if (s->need_gss_transient_hostkey) { + logevent("Post-GSS rekey provided fallback host key:"); + logevent(s->fingerprint); + ssh_store_transient_hostkey(ssh, ssh->hostkey, s->hkey); + s->need_gss_transient_hostkey = FALSE; + } else if (!ssh_verify_transient_hostkey( + ssh, ssh->hostkey, s->hkey)) { + logevent("Non-GSS rekey after initial GSS kex " + "used host key:"); + logevent(s->fingerprint); + bombout(("Host key was not previously sent via GSS kex")); + } + + sfree(s->fingerprint); + s->fingerprint = NULL; + } + } else +#endif /* NO_GSSAPI */ if (!s->got_session_id) { /* * Make a note of any other host key formats that are available. @@ -7434,6 +8091,7 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, * subsequent rekeys. */ ssh->hostkey_str = s->keystr; + s->keystr = NULL; } else if (ssh->cross_certifying) { s->fingerprint = ssh2_fingerprint(ssh->hostkey, s->hkey); logevent("Storing additional host key for this host:"); @@ -7447,6 +8105,7 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, * re-checking in future normal rekeys. */ ssh->hostkey_str = s->keystr; + s->keystr = NULL; } else { /* * In a rekey, we never present an interactive host key @@ -7460,9 +8119,12 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, crStopV; #endif } - sfree(s->keystr); } - ssh->hostkey->freekey(s->hkey); + sfree(s->keystr); + if (s->hkey) { + ssh->hostkey->freekey(s->hkey); + s->hkey = NULL; + } /* * The exchange hash from the very first key exchange is also @@ -7689,29 +8351,45 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, } else { if (inlen == -2) { /* - * authconn has seen a USERAUTH_SUCCEEDED. Time to enable - * delayed compression, if it's available. + * authconn has seen a USERAUTH_SUCCEEDED. For a couple of + * reasons, this may be the moment to do an immediate + * rekey with different parameters. * + * One is to turn on delayed compression. We do this by a + * rekey to work around a protocol design bug: * draft-miller-secsh-compression-delayed-00 says that you - * negotiate delayed compression in the first key exchange, and - * both sides start compressing when the server has sent - * USERAUTH_SUCCESS. This has a race condition -- the server - * can't know when the client has seen it, and thus which incoming - * packets it should treat as compressed. + * negotiate delayed compression in the first key + * exchange, and both sides start compressing when the + * server has sent USERAUTH_SUCCESS. This has a race + * condition -- the server can't know when the client has + * seen it, and thus which incoming packets it should + * treat as compressed. * - * Instead, we do the initial key exchange without offering the - * delayed methods, but note if the server offers them; when we - * get here, if a delayed method was available that was higher - * on our list than what we got, we initiate a rekey in which we - * _do_ list the delayed methods (and hopefully get it as a - * result). Subsequent rekeys will do the same. + * Instead, we do the initial key exchange without + * offering the delayed methods, but note if the server + * offers them; when we get here, if a delayed method was + * available that was higher on our list than what we got, + * we initiate a rekey in which we _do_ list the delayed + * methods (and hopefully get it as a result). Subsequent + * rekeys will do the same. + * + * Another reason for a rekey at this point is if we've + * done a GSS key exchange and don't have anything in our + * transient hostkey cache, in which case we should make + * an attempt to populate the cache now. */ assert(!s->userauth_succeeded); /* should only happen once */ s->userauth_succeeded = TRUE; - if (!s->pending_compression) + if (s->pending_compression) { + in = (void *)"enabling delayed compression"; + } else if (s->need_gss_transient_hostkey) { + in = (void *)"populating transient host key cache"; + } else { /* Can't see any point rekeying. */ goto wait_for_rekey; /* this is utterly horrid */ + } /* else fall through to rekey... */ + s->pending_compression = FALSE; } /* @@ -9246,7 +9924,10 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, int tried_pubkey_config, done_agent; #ifndef NO_GSSAPI int can_gssapi; + int can_gssapi_keyex_auth; int tried_gssapi; + int tried_gssapi_keyex_auth; + time_t gss_cred_expiry; #endif int kbd_inter_refused; int we_are_in, userauth_success; @@ -9271,11 +9952,9 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, struct Packet *pktout; Filename *keyfile; #ifndef NO_GSSAPI - struct ssh_gss_library *gsslib; Ssh_gss_ctx gss_ctx; Ssh_gss_buf gss_buf; Ssh_gss_buf gss_rcvtok, gss_sndtok; - Ssh_gss_name gss_srv_name; Ssh_gss_stat gss_stat; #endif }; @@ -9310,6 +9989,7 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, s->agent_response = NULL; #ifndef NO_GSSAPI s->tried_gssapi = FALSE; + s->tried_gssapi_keyex_auth = FALSE; #endif if (!ssh->bare_connection) { @@ -9744,25 +10424,43 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, s->can_keyb_inter = conf_get_int(ssh->conf, CONF_try_ki_auth) && in_commasep_string("keyboard-interactive", methods, methlen); #ifndef NO_GSSAPI - if (conf_get_int(ssh->conf, CONF_try_gssapi_auth) && - in_commasep_string("gssapi-with-mic", methods, methlen)) { - /* Try loading the GSS libraries and see if we - * have any. */ - if (!ssh->gsslibs) - ssh->gsslibs = ssh_gss_setup(ssh->conf); - s->can_gssapi = (ssh->gsslibs->nlibraries > 0); - } else { - /* No point in even bothering to try to load the - * GSS libraries, if the user configuration and - * server aren't both prepared to attempt GSSAPI - * auth in the first place. */ - s->can_gssapi = FALSE; - } + s->can_gssapi = + conf_get_int(ssh->conf, CONF_try_gssapi_auth) && + in_commasep_string("gssapi-with-mic", methods, methlen) && + ssh->gsslibs->nlibraries > 0; + s->can_gssapi_keyex_auth = + conf_get_int(ssh->conf, CONF_try_gssapi_auth) && + in_commasep_string("gssapi-keyex", methods, methlen) && + ssh->gsslibs->nlibraries > 0 && + ssh->gss_ctx; #endif } ssh->pkt_actx = SSH2_PKTCTX_NOAUTH; +#ifndef NO_GSSAPI + if (s->can_gssapi_keyex_auth && !s->tried_gssapi_keyex_auth) { + + /* gssapi-keyex authentication */ + + s->type = AUTH_TYPE_GSSAPI; + s->tried_gssapi_keyex_auth = TRUE; + ssh->pkt_actx = SSH2_PKTCTX_GSSAPI; + + if (ssh->gsslib->gsslogmsg) + logevent(ssh->gsslib->gsslogmsg); + + logeventf(ssh, "Trying gssapi-keyex..."); + s->pktout = + ssh2_gss_authpacket(ssh, ssh->gss_ctx, "gssapi-keyex"); + ssh2_pkt_send(ssh, s->pktout); + ssh->gsslib->release_cred(ssh->gsslib, &ssh->gss_ctx); + ssh->gss_ctx = NULL; + + continue; + } else +#endif /* NO_GSSAPI */ + if (s->can_pubkey && !s->done_agent && s->nkeys) { /* @@ -10096,47 +10794,21 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, #ifndef NO_GSSAPI } else if (s->can_gssapi && !s->tried_gssapi) { - /* GSSAPI Authentication */ + /* gssapi-with-mic authentication */ - int micoffset, len; + int len; char *data; - Ssh_gss_buf mic; + s->type = AUTH_TYPE_GSSAPI; s->tried_gssapi = TRUE; s->gotit = TRUE; ssh->pkt_actx = SSH2_PKTCTX_GSSAPI; - /* - * Pick the highest GSS library on the preference - * list. - */ - { - int i, j; - s->gsslib = NULL; - for (i = 0; i < ngsslibs; i++) { - int want_id = conf_get_int_int(ssh->conf, - CONF_ssh_gsslist, i); - for (j = 0; j < ssh->gsslibs->nlibraries; j++) - if (ssh->gsslibs->libraries[j].id == want_id) { - s->gsslib = &ssh->gsslibs->libraries[j]; - goto got_gsslib; /* double break */ - } - } - got_gsslib: - /* - * We always expect to have found something in - * the above loop: we only came here if there - * was at least one viable GSS library, and the - * preference list should always mention - * everything and only change the order. - */ - assert(s->gsslib); - } - - if (s->gsslib->gsslogmsg) - logevent(s->gsslib->gsslogmsg); + if (ssh->gsslib->gsslogmsg) + logevent(ssh->gsslib->gsslogmsg); /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */ + logeventf(ssh, "Trying gssapi-with-mic..."); s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); ssh2_pkt_addstring(s->pktout, ssh->username); ssh2_pkt_addstring(s->pktout, "ssh-connection"); @@ -10144,7 +10816,7 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, logevent("Attempting GSSAPI authentication"); /* add mechanism info */ - s->gsslib->indicate_mech(s->gsslib, &s->gss_buf); + ssh->gsslib->indicate_mech(ssh->gsslib, &s->gss_buf); /* number of GSSAPI mechanisms */ ssh2_pkt_adduint32(s->pktout,1); @@ -10179,24 +10851,26 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, continue; } - /* now start running */ - s->gss_stat = s->gsslib->import_name(s->gsslib, - ssh->fullhostname, - &s->gss_srv_name); - if (s->gss_stat != SSH_GSS_OK) { - if (s->gss_stat == SSH_GSS_BAD_HOST_NAME) - logevent("GSSAPI import name failed - Bad service name"); - else - logevent("GSSAPI import name failed"); - continue; + /* Import server name if not cached from KEX */ + if (ssh->gss_srv_name == GSS_C_NO_NAME) { + s->gss_stat = ssh->gsslib->import_name(ssh->gsslib, + ssh->fullhostname, + &ssh->gss_srv_name); + if (s->gss_stat != SSH_GSS_OK) { + if (s->gss_stat == SSH_GSS_BAD_HOST_NAME) + logevent("GSSAPI import name failed -" + " Bad service name"); + else + logevent("GSSAPI import name failed"); + continue; + } } - /* fetch TGT into GSS engine */ - s->gss_stat = s->gsslib->acquire_cred(s->gsslib, &s->gss_ctx); - + /* Allocate our gss_ctx */ + s->gss_stat = ssh->gsslib->acquire_cred(ssh->gsslib, + &s->gss_ctx, NULL); if (s->gss_stat != SSH_GSS_OK) { logevent("GSSAPI authentication failed to get credentials"); - s->gsslib->release_name(s->gsslib, &s->gss_srv_name); continue; } @@ -10206,20 +10880,26 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, /* now enter the loop */ do { - s->gss_stat = s->gsslib->init_sec_context - (s->gsslib, + /* + * When acquire_cred yields no useful expiration, go with + * the service ticket expiration. + */ + s->gss_stat = ssh->gsslib->init_sec_context + (ssh->gsslib, &s->gss_ctx, - s->gss_srv_name, + ssh->gss_srv_name, conf_get_int(ssh->conf, CONF_gssapifwd), &s->gss_rcvtok, - &s->gss_sndtok); + &s->gss_sndtok, + NULL, + NULL); if (s->gss_stat!=SSH_GSS_S_COMPLETE && s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) { logevent("GSSAPI authentication initialisation failed"); - if (s->gsslib->display_status(s->gsslib, s->gss_ctx, - &s->gss_buf) == SSH_GSS_OK) { + if (ssh->gsslib->display_status(ssh->gsslib, + s->gss_ctx, &s->gss_buf) == SSH_GSS_OK) { logevent(s->gss_buf.value); sfree(s->gss_buf.value); } @@ -10228,21 +10908,25 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, } logevent("GSSAPI authentication initialised"); - /* Client and server now exchange tokens until GSSAPI - * no longer says CONTINUE_NEEDED */ - + /* + * Client and server now exchange tokens until GSSAPI + * no longer says CONTINUE_NEEDED + */ if (s->gss_sndtok.length != 0) { - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_TOKEN); + s->pktout = + ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_TOKEN); ssh_pkt_addstring_start(s->pktout); - ssh_pkt_addstring_data(s->pktout,s->gss_sndtok.value,s->gss_sndtok.length); + ssh_pkt_addstring_data(s->pktout, s->gss_sndtok.value, + s->gss_sndtok.length); ssh2_pkt_send(ssh, s->pktout); - s->gsslib->free_tok(s->gsslib, &s->gss_sndtok); + ssh->gsslib->free_tok(ssh->gsslib, &s->gss_sndtok); } if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) { crWaitUntilV(pktin); if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_TOKEN) { - logevent("GSSAPI authentication - bad server response"); + logevent("GSSAPI authentication -" + " bad server response"); s->gss_stat = SSH_GSS_FAILURE; break; } @@ -10253,37 +10937,20 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED); if (s->gss_stat != SSH_GSS_OK) { - s->gsslib->release_name(s->gsslib, &s->gss_srv_name); - s->gsslib->release_cred(s->gsslib, &s->gss_ctx); + ssh->gsslib->release_cred(ssh->gsslib, &s->gss_ctx); continue; } logevent("GSSAPI authentication loop finished OK"); /* Now send the MIC */ - s->pktout = ssh2_pkt_init(0); - micoffset = s->pktout->length; - ssh_pkt_addstring_start(s->pktout); - ssh_pkt_addstring_data(s->pktout, (char *)ssh->v2_session_id, ssh->v2_session_id_len); - ssh_pkt_addbyte(s->pktout, SSH2_MSG_USERAUTH_REQUEST); - ssh_pkt_addstring(s->pktout, ssh->username); - ssh_pkt_addstring(s->pktout, "ssh-connection"); - ssh_pkt_addstring(s->pktout, "gssapi-with-mic"); - - s->gss_buf.value = (char *)s->pktout->data + micoffset; - s->gss_buf.length = s->pktout->length - micoffset; - - s->gsslib->get_mic(s->gsslib, s->gss_ctx, &s->gss_buf, &mic); - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_MIC); - ssh_pkt_addstring_start(s->pktout); - ssh_pkt_addstring_data(s->pktout, mic.value, mic.length); + s->pktout = + ssh2_gss_authpacket(ssh, s->gss_ctx, "gssapi-with-mic"); ssh2_pkt_send(ssh, s->pktout); - s->gsslib->free_mic(s->gsslib, &mic); s->gotit = FALSE; - s->gsslib->release_name(s->gsslib, &s->gss_srv_name); - s->gsslib->release_cred(s->gsslib, &s->gss_ctx); + ssh->gsslib->release_cred(ssh->gsslib, &s->gss_ctx); continue; #endif } else if (s->can_keyb_inter && !s->kbd_inter_refused) { @@ -10699,16 +11366,17 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, if (s->userauth_success && !ssh->bare_connection) { /* - * We've just received USERAUTH_SUCCESS, and we haven't sent any - * packets since. Signal the transport layer to consider enacting - * delayed compression. + * We've just received USERAUTH_SUCCESS, and we haven't sent + * any packets since. Signal the transport layer to consider + * doing an immediate rekey, if it has any reason to want to. * - * (Relying on we_are_in is not sufficient, as - * draft-miller-secsh-compression-delayed is quite clear that it - * triggers on USERAUTH_SUCCESS specifically, and we_are_in can - * become set for other reasons.) + * (Relying on we_are_in is not sufficient. One of the reasons + * to do a post-userauth rekey is OpenSSH delayed compression; + * draft-miller-secsh-compression-delayed is quite clear that + * that triggers on USERAUTH_SUCCESS specifically, and + * we_are_in can become set for other reasons.) */ - do_ssh2_transport(ssh, "enabling delayed compression", -2, NULL); + do_ssh2_transport(ssh, NULL, -2, NULL); } ssh->channels = newtree234(ssh_channelcmp); @@ -11039,6 +11707,34 @@ static void ssh2_protocol_setup(Ssh ssh) { int i; +#ifndef NO_GSSAPI + /* Load and pick the highest GSS library on the preference list. */ + if (!ssh->gsslibs) + ssh->gsslibs = ssh_gss_setup(ssh->conf); + ssh->gsslib = NULL; + if (ssh->gsslibs->nlibraries > 0) { + int i, j; + for (i = 0; i < ngsslibs; i++) { + int want_id = conf_get_int_int(ssh->conf, + CONF_ssh_gsslist, i); + for (j = 0; j < ssh->gsslibs->nlibraries; j++) + if (ssh->gsslibs->libraries[j].id == want_id) { + ssh->gsslib = &ssh->gsslibs->libraries[j]; + goto got_gsslib; /* double break */ + } + } + got_gsslib: + /* + * We always expect to have found something in + * the above loop: we only came here if there + * was at least one viable GSS library, and the + * preference list should always mention + * everything and only change the order. + */ + assert(ssh->gsslib); + } +#endif + /* * Most messages cause SSH2_MSG_UNIMPLEMENTED. */ @@ -11062,6 +11758,7 @@ static void ssh2_protocol_setup(Ssh ssh) /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_GROUP] = ssh2_msg_transport; duplicate case value */ ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_INIT] = ssh2_msg_transport; ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REPLY] = ssh2_msg_transport; + ssh->packet_dispatch[SSH2_MSG_KEXGSS_GROUP] = ssh2_msg_transport; ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = ssh2_msg_unexpected; ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = ssh2_msg_unexpected; ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = ssh2_msg_unexpected; @@ -11135,6 +11832,161 @@ static void ssh2_bare_connection_protocol_setup(Ssh ssh) ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug; } +#ifndef NO_GSSAPI +static struct Packet *ssh2_gss_authpacket(Ssh ssh, Ssh_gss_ctx gss_ctx, + const char *authtype) +{ + struct Packet *p = ssh2_pkt_init(0); + int micoffset = p->length; + Ssh_gss_buf buf; + Ssh_gss_buf mic; + + /* + * The mic is computed over the session id + intended packet, so we + * build an artificial packet with a prepended session id. + */ + ssh_pkt_addstring_start(p); + ssh_pkt_addstring_data(p, (char *)ssh->v2_session_id, + ssh->v2_session_id_len); + ssh_pkt_addbyte(p, SSH2_MSG_USERAUTH_REQUEST); + ssh_pkt_addstring(p, ssh->username); + ssh_pkt_addstring(p, "ssh-connection"); + ssh_pkt_addstring(p, authtype); + + /* Compute the mic */ + buf.value = (char *)p->data + micoffset; + buf.length = p->length - micoffset; + ssh->gsslib->get_mic(ssh->gsslib, gss_ctx, &buf, &mic); + ssh_free_packet(p); + + /* Now we can build the real packet */ + if (strcmp(authtype, "gssapi-with-mic") == 0) { + p = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_MIC); + } else { + p = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(p, ssh->username); + ssh2_pkt_addstring(p, "ssh-connection"); + ssh2_pkt_addstring(p, authtype); + } + ssh_pkt_addstring_start(p); + ssh_pkt_addstring_data(p, (char *)mic.value, mic.length); + + return p; +} + +/* + * This is called at the beginning of each SSH rekey to determine whether we are + * GSS capable, and if we did GSS key exchange, and are delegating credentials, + * it is also called periodically to determine whether we should rekey in order + * to delegate (more) fresh credentials. This is called "credential cascading". + * + * On Windows, with SSPI, we may not get the credential expiration, as Windows + * automatically renews from cached passwords, so the credential effectively + * never expires. Since we still want to cascade when the local TGT is updated, + * we use the expiration of a newly obtained context as a proxy for the + * expiration of the TGT. + */ +static void ssh2_gss_update(Ssh ssh) +{ + int gss_stat; + time_t gss_cred_expiry; + unsigned long mins; + Ssh_gss_buf gss_sndtok; + Ssh_gss_buf gss_rcvtok; + Ssh_gss_ctx gss_ctx; + + ssh->gss_status = 0; + + /* + * Nothing to do if no GSSAPI libraries are configured or GSSAPI auth is not + * enabled. + */ + if (ssh->gsslibs->nlibraries == 0) + return; + if (!conf_get_int(ssh->conf, CONF_try_gssapi_auth)) + return; + + /* Import server name and cache it */ + if (ssh->gss_srv_name == GSS_C_NO_NAME) { + gss_stat = ssh->gsslib->import_name(ssh->gsslib, + ssh->fullhostname, + &ssh->gss_srv_name); + if (gss_stat != SSH_GSS_OK) { + if (gss_stat == SSH_GSS_BAD_HOST_NAME) + logevent("GSSAPI import name failed -" + " Bad service name; won't use GSS key exchange"); + else + logevent("GSSAPI import name failed;" + " won't use GSS key exchange"); + return; + } + } + + /* + * Do we (still) have credentials? Capture the credential expiration when + * available + */ + gss_stat = ssh->gsslib->acquire_cred(ssh->gsslib, + &gss_ctx, + &gss_cred_expiry); + if (gss_stat != SSH_GSS_OK) + return; + + SSH_GSS_CLEAR_BUF(&gss_sndtok); + SSH_GSS_CLEAR_BUF(&gss_rcvtok); + + /* + * When acquire_cred yields no useful expiration, get a proxy for the cred + * expiration from the context expiration. + */ + gss_stat = ssh->gsslib->init_sec_context( + ssh->gsslib, &gss_ctx, ssh->gss_srv_name, + 0 /* don't delegate */, &gss_rcvtok, &gss_sndtok, + (gss_cred_expiry == GSS_NO_EXPIRATION ? &gss_cred_expiry : NULL), + &ssh->gss_ctxt_lifetime); + + /* This context was for testing only. */ + if (gss_ctx) + ssh->gsslib->release_cred(ssh->gsslib, &gss_ctx); + + if (gss_stat != SSH_GSS_OK && + gss_stat != SSH_GSS_S_CONTINUE_NEEDED) { + logeventf(ssh, "GSSAPI init sec context failed;" + " won't use GSS key exchange"); + return; + } + + if (gss_sndtok.length) + ssh->gsslib->free_tok(ssh->gsslib, &gss_sndtok); + + ssh->gss_status |= GSS_KEX_CAPABLE; + + /* + * When rekeying to cascade, avoding doing this too close to the context + * expiration time, since the key exchange might fail. + */ + if (ssh->gss_ctxt_lifetime < MIN_CTXT_LIFETIME) + ssh->gss_status |= GSS_CTXT_MAYFAIL; + + /* + * If we're not delegating credentials, rekeying is not used to refresh + * them. We must avoid setting GSS_CRED_UPDATED or GSS_CTXT_EXPIRES when + * credential delegation is disabled. + */ + if (conf_get_int(ssh->conf, CONF_gssapifwd) == 0) + return; + + if (ssh->gss_cred_expiry != GSS_NO_EXPIRATION && + difftime(gss_cred_expiry, ssh->gss_cred_expiry) > 0) + ssh->gss_status |= GSS_CRED_UPDATED; + + mins = conf_get_int(ssh->conf, CONF_gssapirekey); + mins = rekey_mins(mins, GSS_DEF_REKEY_MINS); + if (mins > 0 && ssh->gss_ctxt_lifetime <= mins * 60) + ssh->gss_status |= GSS_CTXT_EXPIRES; +} +#endif + /* * The rekey_time is zero except when re-configuring. * @@ -11165,6 +12017,30 @@ static int ssh2_timer_update(Ssh ssh, unsigned long rekey_time) ticks = next - now; } +#ifndef NO_GSSAPI + { + unsigned long gssmins; + + /* Check cascade conditions more frequently if configured */ + gssmins = conf_get_int(ssh->conf, CONF_gssapirekey); + gssmins = rekey_mins(gssmins, GSS_DEF_REKEY_MINS); + if (gssmins > 0) { + if (gssmins < mins) + ticks = (mins = gssmins) * 60 * TICKSPERSEC; + + if ((ssh->gss_status & GSS_KEX_CAPABLE) != 0) { + /* + * Run next timer even sooner if it would otherwise be too close + * to the context expiration time + */ + if ((ssh->gss_status & GSS_CTXT_EXPIRES) == 0 && + ssh->gss_ctxt_lifetime - mins * 60 < 2 * MIN_CTXT_LIFETIME) + ticks -= 2 * MIN_CTXT_LIFETIME * TICKSPERSEC; + } + } + } +#endif + /* Schedule the next timer */ ssh->next_rekey = schedule_timer(ticks, ssh2_timer, ssh); return 0; @@ -11173,15 +12049,45 @@ static int ssh2_timer_update(Ssh ssh, unsigned long rekey_time) static void ssh2_timer(void *ctx, unsigned long now) { Ssh ssh = (Ssh)ctx; + unsigned long mins; + unsigned long ticks; - if (ssh->state == SSH_STATE_CLOSED) + if (ssh->state == SSH_STATE_CLOSED || + ssh->kex_in_progress || + ssh->bare_connection || + now != ssh->next_rekey) return; - if (!ssh->kex_in_progress && !ssh->bare_connection && - conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0 && - now == ssh->next_rekey) { + mins = conf_get_int(ssh->conf, CONF_ssh_rekey_time); + mins = rekey_mins(mins, 60); + if (mins == 0) + return; + + /* Rekey if enough time has elapsed */ + ticks = mins * 60 * TICKSPERSEC; + if (now - ssh->last_rekey > ticks - 30*TICKSPERSEC) { do_ssh2_transport(ssh, "timeout", -1, NULL); + return; } + +#ifndef NO_GSSAPI + /* + * Rekey now if we have a new cred or context expires this cycle, but not if + * this is unsafe. + */ + if (conf_get_int(ssh->conf, CONF_gssapirekey)) { + ssh2_gss_update(ssh); + if ((ssh->gss_status & GSS_KEX_CAPABLE) != 0 && + (ssh->gss_status & GSS_CTXT_MAYFAIL) == 0 && + (ssh->gss_status & (GSS_CRED_UPDATED|GSS_CTXT_EXPIRES)) != 0) { + do_ssh2_transport(ssh, GSS_UPDATE_REKEY_REASON, -1, NULL); + return; + } + } +#endif + + /* Try again later. */ + (void) ssh2_timer_update(ssh, 0); } static void ssh2_protocol(Ssh ssh, const void *vin, int inlen, @@ -11315,6 +12221,14 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, ssh->n_uncert_hostkeys = 0; ssh->cross_certifying = FALSE; +#ifndef NO_GSSAPI + ssh->gss_cred_expiry = GSS_NO_EXPIRATION; + ssh->gss_srv_name = GSS_C_NO_NAME; + ssh->gss_ctx = NULL; + ssh_init_transient_hostkey_store(ssh); +#endif + ssh->gss_kex_used = FALSE; + *backend_handle = ssh; #ifdef MSCRYPTOAPI @@ -11478,8 +12392,13 @@ static void ssh_free(void *handle) agent_cancel_query(ssh->auth_agent_query); #ifndef NO_GSSAPI + if (ssh->gss_srv_name) + ssh->gsslib->release_name(ssh->gsslib, &ssh->gss_srv_name); + if (ssh->gss_ctx != NULL) + ssh->gsslib->release_cred(ssh->gsslib, &ssh->gss_ctx); if (ssh->gsslibs) ssh_gss_cleanup(ssh->gsslibs); + ssh_cleanup_transient_hostkey_store(ssh); #endif need_random_unref = ssh->need_random_unref; sfree(ssh); diff --git a/ssh.h b/ssh.h index 2d9c3550..a7902f2c 100644 --- a/ssh.h +++ b/ssh.h @@ -381,7 +381,7 @@ struct ssh_hash { struct ssh_kex { const char *name, *groupname; - enum { KEXTYPE_DH, KEXTYPE_RSA, KEXTYPE_ECDH } main_type; + enum { KEXTYPE_DH, KEXTYPE_RSA, KEXTYPE_ECDH, KEXTYPE_GSS } main_type; const struct ssh_hash *hash; const void *extra; /* private to the kex methods */ }; @@ -466,6 +466,7 @@ extern const struct ssh_hash ssh_sha512; extern const struct ssh_kexes ssh_diffiehellman_group1; extern const struct ssh_kexes ssh_diffiehellman_group14; extern const struct ssh_kexes ssh_diffiehellman_gex; +extern const struct ssh_kexes ssh_gssk5_sha1_kex; extern const struct ssh_kexes ssh_rsa_kex; extern const struct ssh_kexes ssh_ecdh_kex; extern const struct ssh_signkey ssh_dss; @@ -952,6 +953,13 @@ void platform_ssh_share_cleanup(const char *name); #define SSH2_MSG_KEX_DH_GEX_GROUP 31 /* 0x1f */ #define SSH2_MSG_KEX_DH_GEX_INIT 32 /* 0x20 */ #define SSH2_MSG_KEX_DH_GEX_REPLY 33 /* 0x21 */ +#define SSH2_MSG_KEXGSS_INIT 30 /* 0x1e */ +#define SSH2_MSG_KEXGSS_CONTINUE 31 /* 0x1f */ +#define SSH2_MSG_KEXGSS_COMPLETE 32 /* 0x20 */ +#define SSH2_MSG_KEXGSS_HOSTKEY 33 /* 0x21 */ +#define SSH2_MSG_KEXGSS_ERROR 34 /* 0x22 */ +#define SSH2_MSG_KEXGSS_GROUPREQ 40 /* 0x28 */ +#define SSH2_MSG_KEXGSS_GROUP 41 /* 0x29 */ #define SSH2_MSG_KEXRSA_PUBKEY 30 /* 0x1e */ #define SSH2_MSG_KEXRSA_SECRET 31 /* 0x1f */ #define SSH2_MSG_KEXRSA_DONE 32 /* 0x20 */ diff --git a/sshdh.c b/sshdh.c index 3942bd39..46f3a3fc 100644 --- a/sshdh.c +++ b/sshdh.c @@ -121,6 +121,46 @@ const struct ssh_kexes ssh_diffiehellman_gex = { gex_list }; +/* + * Suffix on GSSAPI SSH protocol identifiers that indicates Kerberos 5 + * as the mechanism. + * + * This suffix is the base64-encoded MD5 hash of the byte sequence + * 06 09 2A 86 48 86 F7 12 01 02 02, which in turn is the ASN.1 DER + * encoding of the object ID 1.2.840.113554.1.2.2 which designates + * Kerberos v5. + * + * (The same encoded OID, minus the two-byte DER header, is defined in + * pgssapi.c as GSS_MECH_KRB5.) + */ +#define GSS_KRB5_OID_HASH "toWM5Slw5Ew8Mqkay+al2g==" + +static const struct ssh_kex ssh_gssk5_diffiehellman_gex_sha1 = { + "gss-gex-sha1-" GSS_KRB5_OID_HASH, NULL, + KEXTYPE_GSS, &ssh_sha1, &extra_gex, +}; + +static const struct ssh_kex ssh_gssk5_diffiehellman_group14_sha1 = { + "gss-group14-sha1-" GSS_KRB5_OID_HASH, "group14", + KEXTYPE_GSS, &ssh_sha1, &extra_group14, +}; + +static const struct ssh_kex ssh_gssk5_diffiehellman_group1_sha1 = { + "gss-group1-sha1-" GSS_KRB5_OID_HASH, "group1", + KEXTYPE_GSS, &ssh_sha1, &extra_group1, +}; + +static const struct ssh_kex *const gssk5_sha1_kex_list[] = { + &ssh_gssk5_diffiehellman_gex_sha1, + &ssh_gssk5_diffiehellman_group14_sha1, + &ssh_gssk5_diffiehellman_group1_sha1 +}; + +const struct ssh_kexes ssh_gssk5_sha1_kex = { + sizeof(gssk5_sha1_kex_list) / sizeof(*gssk5_sha1_kex_list), + gssk5_sha1_kex_list +}; + /* * Variables. */ diff --git a/sshgss.h b/sshgss.h index 32ccb4db..11354948 100644 --- a/sshgss.h +++ b/sshgss.h @@ -13,6 +13,8 @@ typedef enum Ssh_gss_stat { SSH_GSS_S_CONTINUE_NEEDED, SSH_GSS_NO_MEM, SSH_GSS_BAD_HOST_NAME, + SSH_GSS_BAD_MIC, + SSH_GSS_NO_CREDS, SSH_GSS_FAILURE } Ssh_gss_stat; @@ -26,6 +28,8 @@ typedef enum Ssh_gss_stat { typedef gss_buffer_desc Ssh_gss_buf; typedef gss_name_t Ssh_gss_name; +#define GSS_NO_EXPIRATION ((time_t)-1) + /* Functions, provided by either wingss.c or sshgssc.c */ struct ssh_gss_library; @@ -79,7 +83,8 @@ typedef Ssh_gss_stat (*t_ssh_gss_release_name)(struct ssh_gss_library *lib, typedef Ssh_gss_stat (*t_ssh_gss_init_sec_context) (struct ssh_gss_library *lib, Ssh_gss_ctx *ctx, Ssh_gss_name name, int delegate, - Ssh_gss_buf *in, Ssh_gss_buf *out); + Ssh_gss_buf *in, Ssh_gss_buf *out, time_t *expiry, + unsigned long *lifetime); /* * Frees the contents of an Ssh_gss_buf filled in by @@ -96,7 +101,8 @@ typedef Ssh_gss_stat (*t_ssh_gss_free_tok)(struct ssh_gss_library *lib, * place. Needs to be freed by ssh_gss_release_cred(). */ typedef Ssh_gss_stat (*t_ssh_gss_acquire_cred)(struct ssh_gss_library *lib, - Ssh_gss_ctx *); + Ssh_gss_ctx *, + time_t *expiry); /* * Frees the contents of an Ssh_gss_ctx filled in by @@ -111,7 +117,15 @@ typedef Ssh_gss_stat (*t_ssh_gss_release_cred)(struct ssh_gss_library *lib, */ typedef Ssh_gss_stat (*t_ssh_gss_get_mic)(struct ssh_gss_library *lib, Ssh_gss_ctx ctx, Ssh_gss_buf *in, - Ssh_gss_buf *out); + Ssh_gss_buf *out); + +/* + * Validates an input MIC for some input data. + */ +typedef Ssh_gss_stat (*t_ssh_gss_verify_mic)(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, + Ssh_gss_buf *in_data, + Ssh_gss_buf *in_mic); /* * Frees the contents of an Ssh_gss_buf filled in by @@ -161,6 +175,7 @@ struct ssh_gss_library { t_ssh_gss_acquire_cred acquire_cred; t_ssh_gss_release_cred release_cred; t_ssh_gss_get_mic get_mic; + t_ssh_gss_verify_mic verify_mic; t_ssh_gss_free_mic free_mic; t_ssh_gss_display_status display_status; diff --git a/sshgssc.c b/sshgssc.c index 4590ed7b..26d301b7 100644 --- a/sshgssc.c +++ b/sshgssc.c @@ -1,6 +1,7 @@ #include "putty.h" #include +#include #include "sshgssc.h" #include "misc.h" @@ -38,14 +39,64 @@ static Ssh_gss_stat ssh_gssapi_import_name(struct ssh_gss_library *lib, } static Ssh_gss_stat ssh_gssapi_acquire_cred(struct ssh_gss_library *lib, - Ssh_gss_ctx *ctx) + Ssh_gss_ctx *ctx, + time_t *expiry) { + struct gssapi_functions *gss = &lib->u.gssapi; + gss_OID_set_desc k5only = { 1, GSS_MECH_KRB5 }; + gss_cred_id_t cred; + OM_uint32 dummy; + OM_uint32 time_rec; gssapi_ssh_gss_ctx *gssctx = snew(gssapi_ssh_gss_ctx); - gssctx->maj_stat = gssctx->min_stat = GSS_S_COMPLETE; gssctx->ctx = GSS_C_NO_CONTEXT; - *ctx = (Ssh_gss_ctx) gssctx; + gssctx->expiry = 0; + gssctx->maj_stat = + gss->acquire_cred(&gssctx->min_stat, GSS_C_NO_NAME, GSS_C_INDEFINITE, + &k5only, GSS_C_INITIATE, &cred, + (gss_OID_set *)0, &time_rec); + + if (gssctx->maj_stat != GSS_S_COMPLETE) { + sfree(gssctx); + return SSH_GSS_FAILURE; + } + + /* + * When the credential lifetime is not yet available due to deferred + * processing, gss_acquire_cred should return a 0 lifetime which is + * distinct from GSS_C_INDEFINITE which signals a crential that never + * expires. However, not all implementations get this right, and with + * Kerberos, initiator credentials always expire at some point. So when + * lifetime is 0 or GSS_C_INDEFINITE we call gss_inquire_cred_by_mech() to + * complete deferred processing. + */ + if (time_rec == GSS_C_INDEFINITE || time_rec == 0) { + gssctx->maj_stat = + gss->inquire_cred_by_mech(&gssctx->min_stat, cred, + (gss_OID) GSS_MECH_KRB5, + GSS_C_NO_NAME, + &time_rec, + NULL, + NULL); + } + (void) gss->release_cred(&dummy, &cred); + + if (gssctx->maj_stat != GSS_S_COMPLETE) { + sfree(gssctx); + return SSH_GSS_FAILURE; + } + + if (time_rec != GSS_C_INDEFINITE) + gssctx->expiry = time(NULL) + time_rec; + else + gssctx->expiry = GSS_NO_EXPIRATION; + + if (expiry) { + *expiry = gssctx->expiry; + } + + *ctx = (Ssh_gss_ctx) gssctx; return SSH_GSS_OK; } @@ -54,11 +105,14 @@ static Ssh_gss_stat ssh_gssapi_init_sec_context(struct ssh_gss_library *lib, Ssh_gss_name srv_name, int to_deleg, Ssh_gss_buf *recv_tok, - Ssh_gss_buf *send_tok) + Ssh_gss_buf *send_tok, + time_t *expiry, + unsigned long *lifetime) { struct gssapi_functions *gss = &lib->u.gssapi; gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx*) *ctx; OM_uint32 ret_flags; + OM_uint32 lifetime_rec; if (to_deleg) to_deleg = GSS_C_DELEG_FLAG; gssctx->maj_stat = gss->init_sec_context(&gssctx->min_stat, @@ -74,7 +128,20 @@ static Ssh_gss_stat ssh_gssapi_init_sec_context(struct ssh_gss_library *lib, NULL, /* ignore mech type */ send_tok, &ret_flags, - NULL); /* ignore time_rec */ + &lifetime_rec); + + if (lifetime) { + if (lifetime_rec == GSS_C_INDEFINITE) + *lifetime = ULONG_MAX; + else + *lifetime = lifetime_rec; + } + if (expiry) { + if (lifetime_rec == GSS_C_INDEFINITE) + *expiry = GSS_NO_EXPIRATION; + else + *expiry = time(NULL) + lifetime_rec; + } if (gssctx->maj_stat == GSS_S_COMPLETE) return SSH_GSS_S_COMPLETE; if (gssctx->maj_stat == GSS_S_CONTINUE_NEEDED) return SSH_GSS_S_CONTINUE_NEEDED; @@ -148,6 +215,7 @@ static Ssh_gss_stat ssh_gssapi_release_cred(struct ssh_gss_library *lib, if (gssctx->ctx != GSS_C_NO_CONTEXT) maj_stat = gss->delete_sec_context(&min_stat,&gssctx->ctx,GSS_C_NO_BUFFER); sfree(gssctx); + *ctx = NULL; if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK; return SSH_GSS_FAILURE; @@ -175,6 +243,16 @@ static Ssh_gss_stat ssh_gssapi_get_mic(struct ssh_gss_library *lib, return gss->get_mic(&(gssctx->min_stat), gssctx->ctx, 0, buf, hash); } +static Ssh_gss_stat ssh_gssapi_verify_mic(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, Ssh_gss_buf *buf, + Ssh_gss_buf *hash) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx; + if (gssctx == NULL) return SSH_GSS_FAILURE; + return gss->verify_mic(&(gssctx->min_stat), gssctx->ctx, buf, hash, NULL); +} + static Ssh_gss_stat ssh_gssapi_free_mic(struct ssh_gss_library *lib, Ssh_gss_buf *hash) { @@ -192,6 +270,7 @@ void ssh_gssapi_bind_fns(struct ssh_gss_library *lib) lib->acquire_cred = ssh_gssapi_acquire_cred; lib->release_cred = ssh_gssapi_release_cred; lib->get_mic = ssh_gssapi_get_mic; + lib->verify_mic = ssh_gssapi_verify_mic; lib->free_mic = ssh_gssapi_free_mic; lib->display_status = ssh_gssapi_display_status; } diff --git a/sshgssc.h b/sshgssc.h index c98ee86f..07fac009 100644 --- a/sshgssc.h +++ b/sshgssc.h @@ -10,6 +10,7 @@ typedef struct gssapi_ssh_gss_ctx { OM_uint32 maj_stat; OM_uint32 min_stat; gss_ctx_id_t ctx; + time_t expiry; } gssapi_ssh_gss_ctx; void ssh_gssapi_bind_fns(struct ssh_gss_library *lib); diff --git a/unix/uxgss.c b/unix/uxgss.c index 2a9e1296..47c59172 100644 --- a/unix/uxgss.c +++ b/unix/uxgss.c @@ -41,11 +41,14 @@ static void gss_init(struct ssh_gss_library *lib, void *dlhandle, BIND_GSS_FN(delete_sec_context); BIND_GSS_FN(display_status); BIND_GSS_FN(get_mic); + BIND_GSS_FN(verify_mic); BIND_GSS_FN(import_name); BIND_GSS_FN(init_sec_context); BIND_GSS_FN(release_buffer); BIND_GSS_FN(release_cred); BIND_GSS_FN(release_name); + BIND_GSS_FN(acquire_cred); + BIND_GSS_FN(inquire_cred_by_mech); #undef BIND_GSS_FN @@ -145,11 +148,14 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) BIND_GSS_FN(delete_sec_context); BIND_GSS_FN(display_status); BIND_GSS_FN(get_mic); + BIND_GSS_FN(verify_mic); BIND_GSS_FN(import_name); BIND_GSS_FN(init_sec_context); BIND_GSS_FN(release_buffer); BIND_GSS_FN(release_cred); BIND_GSS_FN(release_name); + BIND_GSS_FN(acquire_cred); + BIND_GSS_FN(inquire_cred_by_mech); #undef BIND_GSS_FN diff --git a/windows/wingss.c b/windows/wingss.c index 2661ff76..c9bd2b31 100644 --- a/windows/wingss.c +++ b/windows/wingss.c @@ -1,5 +1,6 @@ #ifndef NO_GSSAPI +#include #include "putty.h" #define SECURITY_WIN32 @@ -11,6 +12,22 @@ #include "misc.h" +#define UNIX_EPOCH 11644473600ULL /* Seconds from Windows epoch */ +#define CNS_PERSEC 10000000ULL /* # 100ns per second */ + +/* + * Note, as a special case, 0 relative to the Windows epoch (unspecified) maps + * to 0 relative to the POSIX epoch (unspecified)! + */ +#define TIME_WIN_TO_POSIX(ft, t) do { \ + ULARGE_INTEGER uli; \ + uli.LowPart = (ft).dwLowDateTime; \ + uli.HighPart = (ft).dwHighDateTime; \ + if (uli.QuadPart != 0) \ + uli.QuadPart = uli.QuadPart / CNS_PERSEC - UNIX_EPOCH; \ + (t) = (time_t) uli.QuadPart; \ +} while(0) + /* Windows code to set up the GSSAPI library list. */ #ifdef _WIN64 @@ -55,6 +72,9 @@ DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, MakeSignature, (PCtxtHandle, ULONG, PSecBufferDesc, ULONG)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + VerifySignature, + (PCtxtHandle, PSecBufferDesc, ULONG, PULONG)); DECL_WINDOWS_FUNCTION(static, DLL_DIRECTORY_COOKIE, AddDllDirectory, (PCWSTR)); @@ -144,6 +164,7 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) BIND_GSS_FN(delete_sec_context); BIND_GSS_FN(display_status); BIND_GSS_FN(get_mic); + BIND_GSS_FN(verify_mic); BIND_GSS_FN(import_name); BIND_GSS_FN(init_sec_context); BIND_GSS_FN(release_buffer); @@ -172,6 +193,7 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) GET_WINDOWS_FUNCTION(module, DeleteSecurityContext); GET_WINDOWS_FUNCTION(module, QueryContextAttributesA); GET_WINDOWS_FUNCTION(module, MakeSignature); + GET_WINDOWS_FUNCTION(module, VerifySignature); ssh_sspi_bind_fns(lib); } @@ -224,6 +246,7 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) BIND_GSS_FN(delete_sec_context); BIND_GSS_FN(display_status); BIND_GSS_FN(get_mic); + BIND_GSS_FN(verify_mic); BIND_GSS_FN(import_name); BIND_GSS_FN(init_sec_context); BIND_GSS_FN(release_buffer); @@ -289,7 +312,8 @@ static Ssh_gss_stat ssh_sspi_import_name(struct ssh_gss_library *lib, } static Ssh_gss_stat ssh_sspi_acquire_cred(struct ssh_gss_library *lib, - Ssh_gss_ctx *ctx) + Ssh_gss_ctx *ctx, + time_t *expiry) { winSsh_gss_ctx *winctx = snew(winSsh_gss_ctx); memset(winctx, 0, sizeof(winSsh_gss_ctx)); @@ -309,21 +333,68 @@ static Ssh_gss_stat ssh_sspi_acquire_cred(struct ssh_gss_library *lib, NULL, NULL, &winctx->cred_handle, - &winctx->expiry); + NULL); + + if (winctx->maj_stat != SEC_E_OK) { + p_FreeCredentialsHandle(&winctx->cred_handle); + sfree(winctx); + return SSH_GSS_FAILURE; + } + + /* Windows does not return a valid expiration from AcquireCredentials */ + if (expiry) + *expiry = GSS_NO_EXPIRATION; - if (winctx->maj_stat != SEC_E_OK) return SSH_GSS_FAILURE; - *ctx = (Ssh_gss_ctx) winctx; return SSH_GSS_OK; } +static void localexp_to_exp_lifetime(TimeStamp *localexp, + time_t *expiry, unsigned long *lifetime) +{ + FILETIME nowUTC; + FILETIME expUTC; + time_t now; + time_t exp; + time_t delta; + + if (!lifetime && !expiry) + return; + + GetSystemTimeAsFileTime(&nowUTC); + TIME_WIN_TO_POSIX(nowUTC, now); + + if (lifetime) + *lifetime = 0; + if (expiry) + *expiry = GSS_NO_EXPIRATION; + + if (!LocalFileTimeToFileTime(localexp, &expUTC)) + return; + + TIME_WIN_TO_POSIX(expUTC, exp); + delta = exp - now; + if (exp == 0 || delta <= 0) + return; + + if (expiry) + *expiry = exp; + if (lifetime) { + if (delta <= ULONG_MAX) + *lifetime = (unsigned long)delta; + else + *lifetime = ULONG_MAX; + } +} static Ssh_gss_stat ssh_sspi_init_sec_context(struct ssh_gss_library *lib, Ssh_gss_ctx *ctx, Ssh_gss_name srv_name, int to_deleg, Ssh_gss_buf *recv_tok, - Ssh_gss_buf *send_tok) + Ssh_gss_buf *send_tok, + time_t *expiry, + unsigned long *lifetime) { winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) *ctx; SecBuffer wsend_tok = {send_tok->length,SECBUFFER_TOKEN,send_tok->value}; @@ -333,6 +404,7 @@ static Ssh_gss_stat ssh_sspi_init_sec_context(struct ssh_gss_library *lib, unsigned long flags=ISC_REQ_MUTUAL_AUTH|ISC_REQ_REPLAY_DETECT| ISC_REQ_CONFIDENTIALITY|ISC_REQ_ALLOCATE_MEMORY; unsigned long ret_flags=0; + TimeStamp localexp; /* check if we have to delegate ... */ if (to_deleg) flags |= ISC_REQ_DELEGATE; @@ -347,8 +419,10 @@ static Ssh_gss_stat ssh_sspi_init_sec_context(struct ssh_gss_library *lib, &winctx->context, &output_desc, &ret_flags, - &winctx->expiry); - + &localexp); + + localexp_to_exp_lifetime(&localexp, expiry, lifetime); + /* prepare for the next round */ winctx->context_handle = &winctx->context; send_tok->value = wsend_tok.pvBuffer; @@ -503,6 +577,36 @@ static Ssh_gss_stat ssh_sspi_get_mic(struct ssh_gss_library *lib, return winctx->maj_stat; } +static Ssh_gss_stat ssh_sspi_verify_mic(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, + Ssh_gss_buf *buf, + Ssh_gss_buf *mic) +{ + winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) ctx; + SecBufferDesc InputBufferDescriptor; + SecBuffer InputSecurityToken[2]; + ULONG qop; + + if (winctx == NULL) return SSH_GSS_FAILURE; + + winctx->maj_stat = 0; + + InputBufferDescriptor.cBuffers = 2; + InputBufferDescriptor.pBuffers = InputSecurityToken; + InputBufferDescriptor.ulVersion = SECBUFFER_VERSION; + InputSecurityToken[0].BufferType = SECBUFFER_DATA; + InputSecurityToken[0].cbBuffer = buf->length; + InputSecurityToken[0].pvBuffer = buf->value; + InputSecurityToken[1].BufferType = SECBUFFER_TOKEN; + InputSecurityToken[1].cbBuffer = mic->length; + InputSecurityToken[1].pvBuffer = mic->value; + + winctx->maj_stat = p_VerifySignature(&winctx->context, + &InputBufferDescriptor, + 0, &qop); + return winctx->maj_stat; +} + static Ssh_gss_stat ssh_sspi_free_mic(struct ssh_gss_library *lib, Ssh_gss_buf *hash) { @@ -520,6 +624,7 @@ static void ssh_sspi_bind_fns(struct ssh_gss_library *lib) lib->acquire_cred = ssh_sspi_acquire_cred; lib->release_cred = ssh_sspi_release_cred; lib->get_mic = ssh_sspi_get_mic; + lib->verify_mic = ssh_sspi_verify_mic; lib->free_mic = ssh_sspi_free_mic; lib->display_status = ssh_sspi_display_status; } diff --git a/windows/winhelp.h b/windows/winhelp.h index 3c5fb861..8e4e06fe 100644 --- a/windows/winhelp.h +++ b/windows/winhelp.h @@ -104,6 +104,7 @@ #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_hklist "ssh.hostkey.order:config-ssh-hostkey-order" +#define WINHELP_CTX_ssh_gssapi_kex_delegation "ssh.kex.gssapi.delegation:config-ssh-kex-gssapi-delegation" #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"