diff --git a/crypto/openssh-certs.c b/crypto/openssh-certs.c index c27118a4..73218e7c 100644 --- a/crypto/openssh-certs.c +++ b/crypto/openssh-certs.c @@ -268,7 +268,7 @@ static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, .alternate_ssh_id = opensshcert_alternate_ssh_id, \ .related_alg = opensshcert_related_alg, \ .ssh_id = ssh_alg_id_prefix "-cert-v01@openssh.com", \ - .cache_id = NULL, \ + .cache_id = "opensshcert-" ssh_key_id_prefix, \ .extra = &opensshcert_##name##_extra, \ .is_certificate = true, \ .base_alg = &name, \ @@ -559,8 +559,8 @@ static bool opensshcert_has_private(ssh_key *key) static char *opensshcert_cache_str(ssh_key *key) { - unreachable( - "Certificates are not expected to be stored in the host key cache"); + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + return ssh_key_cache_str(ck->basekey); } static void opensshcert_time_to_iso8601(BinarySink *bs, uint64_t time) diff --git a/ssh.h b/ssh.h index e628638c..454c5449 100644 --- a/ssh.h +++ b/ssh.h @@ -1801,8 +1801,8 @@ bool get_commasep_word(ptrlen *list, ptrlen *word); SeatPromptResult verify_ssh_host_key( InteractionReadySeat iseat, Conf *conf, const char *host, int port, ssh_key *key, const char *keytype, char *keystr, const char *keydisp, - char **fingerprints, void (*callback)(void *ctx, SeatPromptResult result), - void *ctx); + char **fingerprints, int ca_count, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache; ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void); diff --git a/ssh/common.c b/ssh/common.c index 04b341ae..e9823a6d 100644 --- a/ssh/common.c +++ b/ssh/common.c @@ -855,8 +855,8 @@ bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin) SeatPromptResult verify_ssh_host_key( InteractionReadySeat iseat, Conf *conf, const char *host, int port, ssh_key *key, const char *keytype, char *keystr, const char *keydisp, - char **fingerprints, void (*callback)(void *ctx, SeatPromptResult result), - void *ctx) + char **fingerprints, int ca_count, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { /* * First, check if the Conf includes a manual specification of the @@ -934,7 +934,51 @@ SeatPromptResult verify_ssh_host_key( seat_dialog_text_append( text, SDT_TITLE, "%s Security Alert", appname); - if (storage_status == 1) { + if (key && ssh_key_alg(key)->is_certificate) { + seat_dialog_text_append( + text, SDT_SCARY_HEADING, "WARNING - POTENTIAL SECURITY BREACH!"); + seat_dialog_text_append( + text, SDT_PARA, "This server presented a certified host key:"); + seat_dialog_text_append( + text, SDT_DISPLAY, "%s (port %d)", host, port); + if (ca_count) { + seat_dialog_text_append( + text, SDT_PARA, "which was signed by a different " + "certification authority from the %s %s is configured to " + "trust for this server.", ca_count > 1 ? "ones" : "one", + appname); + if (storage_status == 2) { + seat_dialog_text_append( + text, SDT_PARA, "ALSO, that key does not match the key " + "%s had previously cached for this server.", appname); + seat_dialog_text_append( + text, SDT_PARA, "This means that either another " + "certification authority is operating in this realm AND " + "the server administrator has changed the host key, or " + "you have actually connected to another computer " + "pretending to be the server."); + } else { + seat_dialog_text_append( + text, SDT_PARA, "This means that either another " + "certification authority is operating in this realm, or " + "you have actually connected to another computer " + "pretending to be the server."); + } + } else { + assert(storage_status == 2); + seat_dialog_text_append( + text, SDT_PARA, "which does not match the certified key %s " + "had previously cached for this server.", appname); + seat_dialog_text_append( + text, SDT_PARA, "This means that either the server " + "administrator has changed the host key, or you have actually " + "connected to another computer pretending to be the server."); + } + seat_dialog_text_append( + text, SDT_PARA, "The new %s key fingerprint is:", keytype); + seat_dialog_text_append( + text, SDT_DISPLAY, "%s", fingerprints[fptype_default]); + } else if (storage_status == 1) { seat_dialog_text_append( text, SDT_PARA, "The host key is not cached for this server:"); seat_dialog_text_append( diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index a67f9e13..ccc62c3c 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -718,8 +718,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) } } - s->keystr = (s->hkey && !ssh_key_alg(s->hkey)->is_certificate ? - ssh_key_cache_str(s->hkey) : NULL); + s->keystr = ssh_key_cache_str(s->hkey); #ifndef NO_GSSAPI if (s->gss_kex_used) { /* @@ -868,8 +867,6 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * hash.) */ if (ssh_key_alg(s->hkey)->is_certificate) { - ssh2_free_all_fingerprints(fingerprints); - char *base_fp = ssh2_fingerprint(ssh_key_base_key(s->hkey), fptype_default); ppl_logevent("Host key is a certificate, whose base key has " @@ -917,24 +914,26 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) } if (cert_ok) { strbuf_free(error); + ssh2_free_all_fingerprints(fingerprints); ppl_logevent("Accepted certificate"); + goto host_key_ok; } else { ppl_logevent("Rejected host key certificate: %s", error->s); - ssh_sw_abort(s->ppl.ssh, - "Rejected host key certificate: %s", - error->s); - *aborted = true; - strbuf_free(error); - return; + /* now fall through into normal host key checking */ } - } else { + } + + { char *keydisp = ssh2_pubkey_openssh_str(&uk); + int ca_count = ssh_key_alg(s->hkey)->is_certificate ? + count234(s->host_cas) : 0; + s->spr = verify_ssh_host_key( ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport, s->hkey, ssh_key_cache_id(s->hkey), s->keystr, keydisp, - fingerprints, ssh2_transport_dialog_callback, s); + fingerprints, ca_count, ssh2_transport_dialog_callback, s); ssh2_free_all_fingerprints(fingerprints); sfree(keydisp); @@ -947,8 +946,22 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) ssh_spr_close(s->ppl.ssh, s->spr, "host key verification"); return; } + + if (ssh_key_alg(s->hkey)->is_certificate) { + /* + * Explain what's going on in the Event Log: if we + * got here by way of a certified key whose + * certificate we didn't like, then we should + * explain why we chose to continue with the + * connection anyway! + */ + ppl_logevent("Accepting certified host key anyway based " + "on cache"); + } } + host_key_ok: + /* * Save this host key, to check against the one presented in * subsequent rekeys. diff --git a/ssh/login1.c b/ssh/login1.c index d556f22a..d9f3883e 100644 --- a/ssh/login1.c +++ b/ssh/login1.c @@ -243,7 +243,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) s->spr = verify_ssh_host_key( ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport, NULL, - "rsa", keystr, keydisp, fingerprints, + "rsa", keystr, keydisp, fingerprints, 0, ssh1_login_dialog_callback, s); ssh2_free_all_fingerprints(fingerprints); diff --git a/ssh/transport2.c b/ssh/transport2.c index 4383bb98..ab5902b9 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -725,8 +725,8 @@ static void ssh2_write_kexinit_lists( } } - /* Next, add uncertified algorithms we already know a key for - * (unless configured not to do that) */ + /* Next, add algorithms we already know a key for (unless + * configured not to do that) */ warn = false; for (i = 0; i < n_preferred_hk; i++) { if (preferred_hk[i] == HK_WARN) @@ -734,8 +734,8 @@ static void ssh2_write_kexinit_lists( for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { const struct ssh_signkey_with_user_pref_id *a = &ssh2_hostkey_algs[j]; - if (a->alg->is_certificate || !a->alg->cache_id) - continue; + if (a->alg->is_certificate && accept_certs) + continue; /* already added this one */ if (a->id != preferred_hk[i]) continue; if (conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys) &&