diff --git a/crypto/diffie-hellman.c b/crypto/diffie-hellman.c index 772f297a..bf262f2d 100644 --- a/crypto/diffie-hellman.c +++ b/crypto/diffie-hellman.c @@ -223,20 +223,6 @@ static const ssh_kex *const gex_list[] = { const ssh_kexes ssh_diffiehellman_gex = { lenof(gex_list), 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 - * ssh/pgssapi.c as GSS_MECH_KRB5.) - */ -#define GSS_KRB5_OID_HASH "toWM5Slw5Ew8Mqkay+al2g==" - static const ssh_kex ssh_gssk5_diffiehellman_gex_sha1 = { .name = "gss-gex-sha1-" GSS_KRB5_OID_HASH, .groupname = NULL, diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c index b51caab0..a43cb841 100644 --- a/crypto/ecc-ssh.c +++ b/crypto/ecc-ssh.c @@ -1644,6 +1644,14 @@ const ssh_kex ssh_ec_kex_curve25519_libssh = { .ecdh_vt = &ssh_ecdhkex_m_alg, .extra = &kex_extra_curve25519, }; +/* GSSAPI variant */ +static const ssh_kex ssh_ec_kex_curve25519_gss = { + .name = "gss-curve25519-sha256-" GSS_KRB5_OID_HASH, + .main_type = KEXTYPE_GSS_ECDH, + .hash = &ssh_sha256, + .ecdh_vt = &ssh_ecdhkex_m_alg, + .extra = &kex_extra_curve25519, +}; static const struct eckex_extra kex_extra_curve448 = { ec_curve448 }; const ssh_kex ssh_ec_kex_curve448 = { @@ -1669,6 +1677,14 @@ const ssh_kex ssh_ec_kex_nistp256 = { .ecdh_vt = &ssh_ecdhkex_w_alg, .extra = &kex_extra_nistp256, }; +/* GSSAPI variant */ +static const ssh_kex ssh_ec_kex_nistp256_gss = { + .name = "gss-nistp256-sha256-" GSS_KRB5_OID_HASH, + .main_type = KEXTYPE_GSS_ECDH, + .hash = &ssh_sha256, + .ecdh_vt = &ssh_ecdhkex_w_alg, + .extra = &kex_extra_nistp256, +}; static const struct eckex_extra kex_extra_nistp384 = { ec_p384 }; const ssh_kex ssh_ec_kex_nistp384 = { @@ -1678,6 +1694,14 @@ const ssh_kex ssh_ec_kex_nistp384 = { .ecdh_vt = &ssh_ecdhkex_w_alg, .extra = &kex_extra_nistp384, }; +/* GSSAPI variant */ +static const ssh_kex ssh_ec_kex_nistp384_gss = { + .name = "gss-nistp384-sha384-" GSS_KRB5_OID_HASH, + .main_type = KEXTYPE_GSS_ECDH, + .hash = &ssh_sha384, + .ecdh_vt = &ssh_ecdhkex_w_alg, + .extra = &kex_extra_nistp384, +}; static const struct eckex_extra kex_extra_nistp521 = { ec_p521 }; const ssh_kex ssh_ec_kex_nistp521 = { @@ -1687,6 +1711,14 @@ const ssh_kex ssh_ec_kex_nistp521 = { .ecdh_vt = &ssh_ecdhkex_w_alg, .extra = &kex_extra_nistp521, }; +/* GSSAPI variant */ +static const ssh_kex ssh_ec_kex_nistp521_gss = { + .name = "gss-nistp521-sha512-" GSS_KRB5_OID_HASH, + .main_type = KEXTYPE_GSS_ECDH, + .hash = &ssh_sha512, + .ecdh_vt = &ssh_ecdhkex_w_alg, + .extra = &kex_extra_nistp521, +}; static const ssh_kex *const ec_kex_list[] = { &ssh_ec_kex_curve448, @@ -1699,6 +1731,17 @@ static const ssh_kex *const ec_kex_list[] = { const ssh_kexes ssh_ecdh_kex = { lenof(ec_kex_list), ec_kex_list }; +static const ssh_kex *const ec_gss_kex_list[] = { + &ssh_ec_kex_curve25519_gss, + &ssh_ec_kex_nistp521_gss, + &ssh_ec_kex_nistp384_gss, + &ssh_ec_kex_nistp256_gss, +}; + +const ssh_kexes ssh_gssk5_ecdh_kex = { + lenof(ec_gss_kex_list), ec_gss_kex_list +}; + /* ---------------------------------------------------------------------- * Helper functions for finding key algorithms and returning auxiliary * data. diff --git a/ssh.h b/ssh.h index b2955e6a..dbdd7eb1 100644 --- a/ssh.h +++ b/ssh.h @@ -993,6 +993,20 @@ static inline bool ecdh_key_getkey(ecdh_key *key, ptrlen remoteKey, static inline char *ecdh_keyalg_description(const ssh_kex *kex) { return kex->ecdh_vt->description(kex); } +/* + * Suffix on GSSAPI SSH protocol identifiers that indicates Kerberos 5 + * as the mechanism. + * + * This suffix is the base64-encoded MD5 hash of the byte sequence + * 06 09 2A 86 48 86 F7 12 01 02 02, which in turn is the ASN.1 DER + * encoding of the object ID 1.2.840.113554.1.2.2 which designates + * Kerberos v5. + * + * (The same encoded OID, minus the two-byte DER header, is defined in + * ssh/pgssapi.c as GSS_MECH_KRB5.) + */ +#define GSS_KRB5_OID_HASH "toWM5Slw5Ew8Mqkay+al2g==" + /* * Enumeration of signature flags from draft-miller-ssh-agent-02 */ @@ -1159,6 +1173,7 @@ extern const ssh_kex ssh_diffiehellman_group17_sha512; extern const ssh_kex ssh_diffiehellman_group18_sha512; extern const ssh_kexes ssh_gssk5_sha1_kex; extern const ssh_kexes ssh_gssk5_sha2_kex; +extern const ssh_kexes ssh_gssk5_ecdh_kex; extern const ssh_kexes ssh_rsa_kex; extern const ssh_kex ssh_ec_kex_curve25519; extern const ssh_kex ssh_ec_kex_curve448; diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index 0f24881a..3fca162e 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -248,7 +248,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) ecdh_key_free(s->ecdh_key); s->ecdh_key = NULL; #ifndef NO_GSSAPI - } else if (s->kex_alg->main_type == KEXTYPE_GSS) { + } else if (kex_is_gss(s->kex_alg)) { ptrlen data; s->ppl.bpp->pls->kctx = SSH2_PKTCTX_GSSKEX; @@ -276,14 +276,25 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) if (s->nbits > s->kex_alg->hash->hlen * 8) s->nbits = s->kex_alg->hash->hlen * 8; - if (dh_is_gex(s->kex_alg)) { + assert(!s->ecdh_key); + assert(!s->dh_ctx); + + if (s->kex_alg->main_type == KEXTYPE_GSS_ECDH) { + s->ecdh_key = ecdh_key_new(s->kex_alg, false); + + char *desc = ecdh_keyalg_description(s->kex_alg); + ppl_logevent("Doing GSSAPI (with Kerberos V5) %s with hash %s", + desc, ssh_hash_alg(s->exhash)->text_name); + sfree(desc); + } else if (dh_is_gex(s->kex_alg)) { /* * Work out how big a DH group we will need to allow that * much data. */ s->pbits = 512 << ((s->nbits - 1) / 64); ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman " - "group exchange, with minimum %d bits", s->pbits); + "group exchange, with minimum %d bits, and hash %s", + s->pbits, ssh_hash_alg(s->exhash)->text_name); pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXGSS_GROUPREQ); put_uint32(pktout, s->pbits); /* min */ put_uint32(pktout, s->pbits); /* preferred */ @@ -314,14 +325,19 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) } else { s->dh_ctx = dh_setup_group(s->kex_alg); ppl_logevent("Using GSSAPI (with Kerberos V5) Diffie-Hellman with" - " standard group \"%s\"", s->kex_alg->groupname); + " standard group \"%s\" and hash %s", + s->kex_alg->groupname, + ssh_hash_alg(s->exhash)->text_name); } - ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman key " - "exchange with hash %s", ssh_hash_alg(s->exhash)->text_name); /* Now generate e for Diffie-Hellman. */ seat_set_busy_status(s->ppl.seat, BUSY_CPU); - s->e = dh_create_e(s->dh_ctx); + if (s->ecdh_key) { + s->ebuf = strbuf_new_nm(); + ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(s->ebuf)); + } else { + s->e = dh_create_e(s->dh_ctx); + } if (s->shgss->lib->gsslogmsg) ppl_logevent("%s", s->shgss->lib->gsslogmsg); @@ -385,7 +401,11 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) } put_string(pktout, s->gss_sndtok.value, s->gss_sndtok.length); - put_mp_ssh2(pktout, s->e); + if (s->ecdh_key) { + put_stringpl(pktout, ptrlen_from_strbuf(s->ebuf)); + } else { + put_mp_ssh2(pktout, s->e); + } pq_push(s->ppl.out_pq, pktout); s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok); ppl_logevent("GSSAPI key exchange initialised"); @@ -412,7 +432,11 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) continue; case SSH2_MSG_KEXGSS_COMPLETE: s->complete_rcvd = true; - s->f = get_mp_ssh2(pktin); + if (s->ecdh_key) { + s->fbuf = strbuf_dup_nm(get_string(pktin)); + } else { + s->f = get_mp_ssh2(pktin); + } data = get_string(pktin); s->mic.value = (char *)data.ptr; s->mic.length = data.len; @@ -475,7 +499,16 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED || !s->complete_rcvd); - { + if (s->ecdh_key) { + bool ok = ecdh_key_getkey(s->ecdh_key, ptrlen_from_strbuf(s->fbuf), + BinarySink_UPCAST(s->kex_shared_secret)); + if (!ok) { + ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve " + "point in GSSAPI ECDH reply"); + *aborted = true; + return; + } + } else { const char *err = dh_validate_f(s->dh_ctx, s->f); if (err) { ssh_proto_error(s->ppl.ssh, "GSSAPI reply failed " @@ -483,10 +516,10 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) *aborted = true; return; } + mp_int *K = dh_find_K(s->dh_ctx, s->f); + put_mp_ssh2(s->kex_shared_secret, K); + mp_free(K); } - mp_int *K = dh_find_K(s->dh_ctx, s->f); - put_mp_ssh2(s->kex_shared_secret, K); - mp_free(K); /* We assume everything from now on will be quick, and it might * involve user interaction. */ @@ -494,26 +527,39 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) if (!s->hkey) put_stringz(s->exhash, ""); - if (dh_is_gex(s->kex_alg)) { - /* min, preferred, max */ - put_uint32(s->exhash, s->pbits); - put_uint32(s->exhash, s->pbits); - put_uint32(s->exhash, s->pbits * 2); - put_mp_ssh2(s->exhash, s->p); - put_mp_ssh2(s->exhash, s->g); + if (s->ecdh_key) { + put_stringpl(s->exhash, ptrlen_from_strbuf(s->ebuf)); + put_stringpl(s->exhash, ptrlen_from_strbuf(s->fbuf)); + } else { + if (dh_is_gex(s->kex_alg)) { + /* min, preferred, max */ + put_uint32(s->exhash, s->pbits); + put_uint32(s->exhash, s->pbits); + put_uint32(s->exhash, s->pbits * 2); + + put_mp_ssh2(s->exhash, s->p); + put_mp_ssh2(s->exhash, s->g); + } + put_mp_ssh2(s->exhash, s->e); + put_mp_ssh2(s->exhash, s->f); } - put_mp_ssh2(s->exhash, s->e); - put_mp_ssh2(s->exhash, s->f); /* * MIC verification is done below, after we compute the hash * used as the MIC input. */ - dh_cleanup(s->dh_ctx); - s->dh_ctx = NULL; - mp_free(s->f); s->f = NULL; + if (s->ecdh_key) { + ecdh_key_free(s->ecdh_key); + s->ecdh_key = NULL; + strbuf_free(s->ebuf); s->ebuf = NULL; + strbuf_free(s->fbuf); s->fbuf = NULL; + } else { + dh_cleanup(s->dh_ctx); + s->dh_ctx = NULL; + mp_free(s->f); s->f = NULL; + } if (dh_is_gex(s->kex_alg)) { mp_free(s->g); s->g = NULL; mp_free(s->p); s->p = NULL; @@ -646,7 +692,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) ssh2transport_finalise_exhash(s); #ifndef NO_GSSAPI - if (s->kex_alg->main_type == KEXTYPE_GSS) { + if (kex_is_gss(s->kex_alg)) { Ssh_gss_buf gss_buf; SSH_GSS_CLEAR_BUF(&s->gss_buf); @@ -694,7 +740,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) s->dh_ctx = NULL; /* In GSS keyex there's no hostkey signature to verify */ - if (s->kex_alg->main_type != KEXTYPE_GSS) { + if (!kex_is_gss(s->kex_alg)) { if (!s->hkey) { ssh_proto_error(s->ppl.ssh, "Server's host key is invalid"); *aborted = true; @@ -720,7 +766,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * In a GSS-based session, check the host key (if any) against * the transient host key cache. */ - if (s->kex_alg->main_type == KEXTYPE_GSS) { + if (kex_is_gss(s->kex_alg)) { /* * We've just done a GSS key exchange. If it gave us a @@ -785,7 +831,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * An exception is if this was the non-GSS key exchange we * triggered on purpose to populate the transient cache. */ - assert(s->hkey); /* only KEXTYPE_GSS lets this be null */ + assert(s->hkey); /* only KEXTYPE_GSS* lets this be null */ char *fingerprint = ssh2_double_fingerprint( s->hkey, SSH_FPTYPE_DEFAULT); diff --git a/ssh/transport2.c b/ssh/transport2.c index 2fd8f65d..37093978 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -228,6 +228,8 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl) if (s->f) mp_free(s->f); if (s->p) mp_free(s->p); if (s->g) mp_free(s->g); + if (s->ebuf) strbuf_free(s->ebuf); + if (s->fbuf) strbuf_free(s->fbuf); if (s->kex_shared_secret) strbuf_free(s->kex_shared_secret); if (s->dh_ctx) dh_cleanup(s->dh_ctx); @@ -508,7 +510,7 @@ static void ssh2_write_kexinit_lists( bool warn; int n_preferred_kex; - const ssh_kexes *preferred_kex[KEX_MAX + 2]; /* +2 for GSSAPI */ + const ssh_kexes *preferred_kex[KEX_MAX + 3]; /* +3 for GSSAPI */ int n_preferred_hk; int preferred_hk[HK_MAX]; int n_preferred_ciphers; @@ -524,6 +526,7 @@ static void ssh2_write_kexinit_lists( */ n_preferred_kex = 0; if (can_gssapi_keyex) { + preferred_kex[n_preferred_kex++] = &ssh_gssk5_ecdh_kex; preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha2_kex; preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha1_kex; } diff --git a/ssh/transport2.h b/ssh/transport2.h index a5f85103..7ea39cbb 100644 --- a/ssh/transport2.h +++ b/ssh/transport2.h @@ -181,6 +181,7 @@ struct ssh2_transport_state { int nbits, pbits; bool warn_kex, warn_hk, warn_cscipher, warn_sccipher; mp_int *p, *g, *e, *f; + strbuf *ebuf, *fbuf; strbuf *kex_shared_secret; strbuf *outgoing_kexinit, *incoming_kexinit; strbuf *client_kexinit, *server_kexinit; /* aliases to the above */