[RFC] Preferentially TOFU certificate authorities rather than host keys
Matthew Garrett
mjg59 at srcf.ucam.org
Tue Oct 15 02:29:56 AEDT 2024
There's currently no way to express trust for an SSH certificate CA other
than by manually adding it to known_hosts. This patch modifies the automatic
key write-out behaviour on user verification to associate the hostname with
the CA rather than the host key, allowing environments making use of
certificates to update (potentially compromised) host keys without needing
to modify client configuration or force users to update their known_hosts.
---
Posting at this point primarily for discussion rather than submission -
if this is something that seems desirable then we probably also want the
ability for servers to revoke old certificates. I have some patches for
that, but don't want to spend too much time cleaning them up unless this
seems like something that stands some chance of being accepted.
This was inspired by
https://github.blog/news-insights/company-news/we-updated-our-rsa-ssh-host-key/
- github accidentally leaked their RSA private key into a public repo
and immediately rolled over to a new one. This created significant
disruption to client systems which fired noisy warnings about host keys
having changed, and organisations were forced to reassure their users
that in this specific case they should go ahead and delete the old
fingerprint but should still in general not do that. Everyone had a bad
day.
If Github had been using certificates, and if we had a way to engender
trust in certificate CAs, they could have rolled to a new key and
certificate signed with the same (hopefully offline) CA key with zero
impact on users. Ideally they'd also be able to push a signed revocation
statement that would invalidate the old certificate.
hostfile.c | 9 +++++++--
sshconnect.c | 30 +++++++++++++++++++++++-------
sshkey.c | 6 ++++++
sshkey.h | 1 +
4 files changed, 37 insertions(+), 9 deletions(-)
diff --git a/hostfile.c b/hostfile.c
index c5669c703..462ed8357 100644
--- a/hostfile.c
+++ b/hostfile.c
@@ -437,12 +437,15 @@ static int
write_host_entry(FILE *f, const char *host, const char *ip,
const struct sshkey *key, int store_hash)
{
- int r, success = 0;
+ int r, success = 0, cert = sshkey_is_cert(key);
char *hashed_host = NULL, *lhost;
lhost = xstrdup(host);
lowercase(lhost);
+ if (cert)
+ fprintf(f, "%s ", CA_MARKER);
+
if (store_hash) {
if ((hashed_host = host_hash(lhost, NULL, 0)) == NULL) {
error_f("host_hash failed");
@@ -457,7 +460,9 @@ write_host_entry(FILE *f, const char *host, const char *ip,
}
free(hashed_host);
free(lhost);
- if ((r = sshkey_write(key, f)) == 0)
+ if ((cert && (r = sshca_write(key, f)) == 0))
+ success = 1;
+ else if ((r = sshkey_write(key, f) == 0))
success = 1;
else
error_fr(r, "sshkey_write");
diff --git a/sshconnect.c b/sshconnect.c
index 7cf6b6386..72bdc7d1f 100644
--- a/sshconnect.c
+++ b/sshconnect.c
@@ -964,7 +964,7 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo,
HostStatus host_status = -1, ip_status = -1;
struct sshkey *raw_key = NULL;
char *ip = NULL, *host = NULL;
- char hostline[1000], *hostp, *fp, *ra;
+ char hostline[1000], *hostp, *fp, *cafp, *ra;
char msg[1024];
const char *type, *fail_reason = NULL;
const struct hostkey_entry *host_found = NULL, *ip_found = NULL;
@@ -973,6 +973,7 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo,
int r, want_cert = sshkey_is_cert(host_key), host_ip_differ = 0;
int hostkey_trusted = 0; /* Known or explicitly accepted by user */
struct hostkeys *host_hostkeys, *ip_hostkeys;
+ struct sshkey *cert = NULL;
u_int i;
/*
@@ -1189,13 +1190,20 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo,
"type are already known for this host.");
} else
xextendf(&msg1, "", ".");
-
fp = sshkey_fingerprint(host_key,
options.fingerprint_hash, SSH_FP_DEFAULT);
ra = sshkey_fingerprint(host_key,
options.fingerprint_hash, SSH_FP_RANDOMART);
if (fp == NULL || ra == NULL)
fatal_f("sshkey_fingerprint failed");
+ if (cert) {
+ cafp = sshkey_fingerprint(cert->cert->signature_key,
+ options.fingerprint_hash, SSH_FP_DEFAULT);
+ if (cafp == NULL)
+ fatal_f("sshkey_fingerprint failed");
+ xextendf(&msg1, "\n", "%s CA certificate fingerprint is %s.",
+ type, cafp);
+ }
xextendf(&msg1, "\n", "%s key fingerprint is %s.",
type, fp);
if (options.visual_host_key)
@@ -1229,19 +1237,26 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo,
* If in "new" or "off" strict mode, add the key automatically
* to the local known_hosts file.
*/
+ if (cert)
+ host_key = cert;
if (options.check_host_ip && ip_status == HOST_NEW) {
snprintf(hostline, sizeof(hostline), "%s,%s", host, ip);
hostp = hostline;
if (options.hash_known_hosts) {
/* Add hash of host and IP separately */
r = add_host_to_hostfile(user_hostfiles[0],
- host, host_key, options.hash_known_hosts) &&
- add_host_to_hostfile(user_hostfiles[0], ip,
- host_key, options.hash_known_hosts);
+ host, host_key, options.hash_known_hosts);
+ /* Don't add an IP entry if we're writing out a cert */
+ if (!r && !cert) {
+ r = add_host_to_hostfile(user_hostfiles[0], ip,
+ host_key, options.hash_known_hosts);
+ }
} else {
- /* Add unhashed "host,ip" */
+ if (cert)
+ /* Certificates are host-specific */
+ hostp = host;
r = add_host_to_hostfile(user_hostfiles[0],
- hostline, host_key,
+ hostp, host_key,
options.hash_known_hosts);
}
} else {
@@ -1453,6 +1468,7 @@ fail:
fatal_fr(r, "decode key");
if ((r = sshkey_drop_cert(raw_key)) != 0)
fatal_r(r, "Couldn't drop certificate");
+ cert = host_key;
host_key = raw_key;
goto retry;
}
diff --git a/sshkey.c b/sshkey.c
index 73fb89ac2..903611937 100644
--- a/sshkey.c
+++ b/sshkey.c
@@ -1427,6 +1427,12 @@ sshkey_format_text(const struct sshkey *key, struct sshbuf *b)
return r;
}
+int
+sshca_write(const struct sshkey *key, FILE *f)
+{
+ return sshkey_write(key->cert->signature_key, f);
+}
+
int
sshkey_write(const struct sshkey *key, FILE *f)
{
diff --git a/sshkey.h b/sshkey.h
index d0cdea0ce..71a111b8b 100644
--- a/sshkey.h
+++ b/sshkey.h
@@ -212,6 +212,7 @@ int sshkey_fingerprint_raw(const struct sshkey *k,
const char *sshkey_type(const struct sshkey *);
const char *sshkey_cert_type(const struct sshkey *);
int sshkey_format_text(const struct sshkey *, struct sshbuf *);
+int sshca_write(const struct sshkey *, FILE *);
int sshkey_write(const struct sshkey *, FILE *);
int sshkey_read(struct sshkey *, char **);
u_int sshkey_size(const struct sshkey *);
--
2.46.2
More information about the openssh-unix-dev
mailing list