RFC: encrypted hostkeys patch

Zev Weiss zev at bewilderbeest.net
Tue Jun 25 15:06:07 EST 2013


Hi,

About a year and a half ago I brought up the topic of encrypted hostkeys
and posted a patch
(http://marc.info/?l=openssh-unix-dev&m=132774431906364&w=2), and while the
general reaction seemed receptive to the idea, a few problems were pointed
out with the implementation (UI issues, ssh-keysign breakage).

I've finally had some spare time in which to get back to this, and I've
written a new patch which has the daemon talking to an ssh-agent for
private key operations, as suggested in the previous conversation -- the
current version of the patch can be found below.  It's not complete
(doesn't address ssh-keysign, for one thing), but I was hoping for some
feedback on it so far -- reasonable-looking in terms of general approach?

I had initially implemented a somewhat simpler version with the privsep
child talking directly to the agent, but then figured that exposing the
agent socket directly to the network-facing process might not be such a
good idea security-wise (in contrast to the much more restricted protocol
of the privsep monitor), so I rearranged things so that only the monitor
has the agent connection.  This version also (somewhat unnecessarily)
bundles public keys into the sensitive_data struct, but I didn't really see
a more appropriate place to stash those.

And, assuming things look OK thus far, I'm considering how best to handle
the ssh-keysign problem.  Since it's executed by a user's ssh client, it
won't have the server's SSH_AUTH_SOCK environment variable, so finding the
socket to connect to is slightly tricky -- any problems with changing it to
a (configurable) static, globally-known path?  Assuming not, then there's
the question of *where* that would be configured -- sshd would need to know
it, but ssh-keysign reads ssh_config, not sshd_config; requiring the user
to configure the same path in both seems undesirable, as does having either
one loading the other's config file.  I guess making it compile-time
configurable would sort of work, but also doesn't seem like a great
solution.  Any thoughts or suggestions on this?  Having a static,
configurable socket path does seem nice otherwise, so sshd could just spawn
its own agent passing "-a $SOCKETPATH" if it encounters an encrypted
hostkey on startup, rather than, say, relying on an init script to launch
ssh-agent and export the SSH_AUTH_SOCK variable to sshd (though I suppose
there's really nothing stopping it from doing that anyway without a static
socket path).

Thoughts/comments welcome.


Thanks,
Zev Weiss


diff --git a/auth.h b/auth.h
index a406e13..d91f845 100644
--- a/auth.h
+++ b/auth.h
@@ -197,6 +197,7 @@ check_key_in_hostfiles(struct passwd *, Key *, const char *,
 
 /* hostkey handling */
 Key	*get_hostkey_by_index(int);
+Key	*get_hostkey_public_by_index(int);
 Key	*get_hostkey_public_by_type(int);
 Key	*get_hostkey_private_by_type(int);
 int	 get_hostkey_index(Key *);
diff --git a/kex.h b/kex.h
index 680264a..b77a2c2 100644
--- a/kex.h
+++ b/kex.h
@@ -139,6 +139,7 @@ struct Kex {
 	Key	*(*load_host_public_key)(int);
 	Key	*(*load_host_private_key)(int);
 	int	(*host_key_index)(Key *);
+	void    (*sign)(Key *, Key *, u_char **, u_int *, u_char *, u_int);
 	void	(*kex[KEX_MAX])(Kex *);
 };
 
diff --git a/kexdhs.c b/kexdhs.c
index 1512863..f6d43f2 100644
--- a/kexdhs.c
+++ b/kexdhs.c
@@ -80,9 +80,6 @@ kexdh_server(Kex *kex)
 	if (server_host_public == NULL)
 		fatal("Unsupported hostkey type %d", kex->hostkey_type);
 	server_host_private = kex->load_host_private_key(kex->hostkey_type);
-	if (server_host_private == NULL)
-		fatal("Missing private key for hostkey type %d",
-		    kex->hostkey_type);
 
 	/* key, cert */
 	if ((dh_client_pub = BN_new()) == NULL)
@@ -144,9 +141,8 @@ kexdh_server(Kex *kex)
 	}
 
 	/* sign H */
-	if (PRIVSEP(key_sign(server_host_private, &signature, &slen, hash,
-	    hashlen)) < 0)
-		fatal("kexdh_server: key_sign failed");
+	kex->sign(server_host_private, server_host_public, &signature, &slen,
+	    hash, hashlen);
 
 	/* destroy_sensitive_data(); */
 
diff --git a/kexecdhs.c b/kexecdhs.c
index c42dcf4..eec5fb6 100644
--- a/kexecdhs.c
+++ b/kexecdhs.c
@@ -78,9 +78,6 @@ kexecdh_server(Kex *kex)
 	if (server_host_public == NULL)
 		fatal("Unsupported hostkey type %d", kex->hostkey_type);
 	server_host_private = kex->load_host_private_key(kex->hostkey_type);
-	if (server_host_private == NULL)
-		fatal("Missing private key for hostkey type %d",
-		    kex->hostkey_type);
 
 	debug("expecting SSH2_MSG_KEX_ECDH_INIT");
 	packet_read_expect(SSH2_MSG_KEX_ECDH_INIT);
@@ -139,9 +136,8 @@ kexecdh_server(Kex *kex)
 	}
 
 	/* sign H */
-	if (PRIVSEP(key_sign(server_host_private, &signature, &slen,
-	    hash, hashlen)) < 0)
-		fatal("kexdh_server: key_sign failed");
+	kex->sign(server_host_private, server_host_public, &signature, &slen,
+	    hash, hashlen);
 
 	/* destroy_sensitive_data(); */
 
diff --git a/kexgexs.c b/kexgexs.c
index a543dda..3ef7710 100644
--- a/kexgexs.c
+++ b/kexgexs.c
@@ -68,10 +68,6 @@ kexgex_server(Kex *kex)
 	if (server_host_public == NULL)
 		fatal("Unsupported hostkey type %d", kex->hostkey_type);
 	server_host_private = kex->load_host_private_key(kex->hostkey_type);
-	if (server_host_private == NULL)
-		fatal("Missing private key for hostkey type %d",
-		    kex->hostkey_type);
-
 
 	type = packet_read();
 	switch (type) {
@@ -187,9 +183,8 @@ kexgex_server(Kex *kex)
 	}
 
 	/* sign H */
-	if (PRIVSEP(key_sign(server_host_private, &signature, &slen, hash,
-	    hashlen)) < 0)
-		fatal("kexgex_server: key_sign failed");
+	kex->sign(server_host_private, server_host_public, &signature, &slen,
+	    hash, hashlen);
 
 	/* destroy_sensitive_data(); */
 
diff --git a/monitor.c b/monitor.c
index 7286126..8f289b5 100644
--- a/monitor.c
+++ b/monitor.c
@@ -97,6 +97,7 @@
 #include "ssh2.h"
 #include "jpake.h"
 #include "roaming.h"
+#include "authfd.h"
 
 #ifdef GSSAPI
 static Gssctxt *gsscontext = NULL;
@@ -686,6 +687,8 @@ mm_answer_moduli(int sock, Buffer *m)
 	return (0);
 }
 
+extern AuthenticationConnection *auth_conn;
+
 int
 mm_answer_sign(int sock, Buffer *m)
 {
@@ -714,10 +717,16 @@ mm_answer_sign(int sock, Buffer *m)
 		memcpy(session_id2, p, session_id2_len);
 	}
 
-	if ((key = get_hostkey_by_index(keyid)) == NULL)
+	if ((key = get_hostkey_by_index(keyid)) != NULL) {
+		if (key_sign(key, &signature, &siglen, p, datlen) < 0)
+			fatal("%s: key_sign failed", __func__);
+	} else if ((key = get_hostkey_public_by_index(keyid)) != NULL &&
+	    auth_conn != NULL) {
+		if (ssh_agent_sign(auth_conn, key, &signature, &siglen, p,
+		    datlen) < 0)
+			fatal("%s: ssh_agent_sign failed", __func__);
+	} else
 		fatal("%s: no hostkey from index %d", __func__, keyid);
-	if (key_sign(key, &signature, &siglen, p, datlen) < 0)
-		fatal("%s: key_sign failed", __func__);
 
 	debug3("%s: signature %p(%u)", __func__, signature, siglen);
 
diff --git a/sshd.c b/sshd.c
index 1306a62..a4a788b 100644
--- a/sshd.c
+++ b/sshd.c
@@ -106,6 +106,7 @@
 #include "canohost.h"
 #include "hostfile.h"
 #include "auth.h"
+#include "authfd.h"
 #include "misc.h"
 #include "msg.h"
 #include "dispatch.h"
@@ -194,6 +195,9 @@ char *server_version_string = NULL;
 /* for rekeying XXX fixme */
 Kex *xxx_kex;
 
+/* Daemon's agent connection */
+AuthenticationConnection *auth_conn = NULL;
+
 /*
  * Any really sensitive data in the application is contained in this
  * structure. The idea is that this structure could be locked into memory so
@@ -206,6 +210,7 @@ struct {
 	Key	*server_key;		/* ephemeral server key */
 	Key	*ssh1_host_key;		/* ssh1 host key */
 	Key	**host_keys;		/* all private host keys */
+	Key	**host_pubkeys;		/* all public host keys */
 	Key	**host_certificates;	/* all public host certificates */
 	int	have_ssh1_key;
 	int	have_ssh2_key;
@@ -652,11 +657,18 @@ privsep_preauth(Authctxt *authctxt)
 	} else if (pid != 0) {
 		debug2("Network child is on pid %ld", (long)pid);
 
+		auth_conn = ssh_get_authentication_connection();
+
 		pmonitor->m_pid = pid;
 		if (box != NULL)
 			ssh_sandbox_parent_preauth(box, pid);
 		monitor_child_preauth(authctxt, pmonitor);
 
+		if (auth_conn) {
+			ssh_close_authentication_connection(auth_conn);
+			auth_conn = NULL;
+		}
+
 		/* Sync memory */
 		monitor_sync(pmonitor);
 
@@ -704,10 +716,11 @@ privsep_postauth(Authctxt *authctxt)
 	u_int32_t rnd[256];
 
 #ifdef DISABLE_FD_PASSING
-	if (1) {
+	if (1)
 #else
-	if (authctxt->pw->pw_uid == 0 || options.use_login) {
+	if (authctxt->pw->pw_uid == 0 || options.use_login)
 #endif
+	{
 		/* File descriptor passing is broken or root login */
 		use_privsep = 0;
 		goto skip;
@@ -767,6 +780,8 @@ list_hostkey_types(void)
 	for (i = 0; i < options.num_host_key_files; i++) {
 		key = sensitive_data.host_keys[i];
 		if (key == NULL)
+			key = sensitive_data.host_pubkeys[i];
+		if (key == NULL)
 			continue;
 		switch (key->type) {
 		case KEY_RSA:
@@ -819,6 +834,8 @@ get_hostkey_by_type(int type, int need_private)
 			break;
 		default:
 			key = sensitive_data.host_keys[i];
+			if (key == NULL && !need_private)
+				key = sensitive_data.host_pubkeys[i];
 			break;
 		}
 		if (key != NULL && key->type == type)
@@ -848,6 +865,14 @@ get_hostkey_by_index(int ind)
 	return (sensitive_data.host_keys[ind]);
 }
 
+Key *
+get_hostkey_public_by_index(int ind)
+{
+	if (ind < 0 || ind >= options.num_host_key_files)
+		return (NULL);
+	return (sensitive_data.host_pubkeys[ind]);
+}
+
 int
 get_hostkey_index(Key *key)
 {
@@ -860,6 +885,8 @@ get_hostkey_index(Key *key)
 		} else {
 			if (key == sensitive_data.host_keys[i])
 				return (i);
+			if (key == sensitive_data.host_pubkeys[i])
+				return (i);
 		}
 	}
 	return (-1);
@@ -1344,6 +1371,9 @@ main(int ac, char **av)
 	u_int64_t ibytes, obytes;
 	mode_t new_umask;
 	Key *key;
+	Key *pubkey;
+	char *pubkey_comment;
+	int have_agent, keytype;
 	Authctxt *authctxt;
 	struct connection_info *connection_info = get_connection_info(0, 0);
 
@@ -1623,22 +1653,40 @@ main(int ac, char **av)
 	}
 	endpwent();
 
-	/* load private host keys */
+	/* load host keys */
 	sensitive_data.host_keys = xcalloc(options.num_host_key_files,
 	    sizeof(Key *));
-	for (i = 0; i < options.num_host_key_files; i++)
+	sensitive_data.host_pubkeys = xcalloc(options.num_host_key_files,
+	    sizeof(Key *));
+	for (i = 0; i < options.num_host_key_files; i++) {
 		sensitive_data.host_keys[i] = NULL;
+		sensitive_data.host_pubkeys[i] = NULL;
+	}
+
+	have_agent = ssh_agent_present();
 
 	for (i = 0; i < options.num_host_key_files; i++) {
 		key = key_load_private(options.host_key_files[i], "", NULL);
+		pubkey = key_load_public(options.host_key_files[i],
+		    &pubkey_comment);
 		sensitive_data.host_keys[i] = key;
-		if (key == NULL) {
+		sensitive_data.host_pubkeys[i] = pubkey;
+
+		if (key == NULL && pubkey != NULL && pubkey->type != KEY_RSA1 &&
+		    have_agent) {
+			debug("will rely on agent for hostkey %s",
+			    options.host_key_files[i]);
+			keytype = pubkey->type;
+		} else if (key == NULL) {
 			error("Could not load host key: %s",
 			    options.host_key_files[i]);
 			sensitive_data.host_keys[i] = NULL;
+			sensitive_data.host_pubkeys[i] = NULL;
 			continue;
-		}
-		switch (key->type) {
+		} else
+			keytype = key->type;
+
+		switch (keytype) {
 		case KEY_RSA1:
 			sensitive_data.ssh1_host_key = key;
 			sensitive_data.have_ssh1_key = 1;
@@ -1649,8 +1697,8 @@ main(int ac, char **av)
 			sensitive_data.have_ssh2_key = 1;
 			break;
 		}
-		debug("private host key: #%d type %d %s", i, key->type,
-		    key_type(key));
+		debug("private host key: #%d type %d %s", i, keytype,
+		    key_type(key ? key : pubkey));
 	}
 	if ((options.protocol & SSH_PROTO_1) && !sensitive_data.have_ssh1_key) {
 		logit("Disabling protocol version 1. Could not load host key");
@@ -2020,15 +2068,21 @@ main(int ac, char **av)
 	buffer_init(&loginmsg);
 	auth_debug_reset();
 
-	if (use_privsep)
+	if (use_privsep) {
 		if (privsep_preauth(authctxt) == 1)
 			goto authenticated;
+	} else if (compat20)
+		auth_conn = ssh_get_authentication_connection();
 
 	/* perform the key exchange */
 	/* authenticate user and start session */
 	if (compat20) {
 		do_ssh2_kex();
 		do_authentication2(authctxt);
+		if (!use_privsep && auth_conn) {
+			ssh_close_authentication_connection(auth_conn);
+			auth_conn = NULL;
+		}
 	} else {
 		do_ssh1_kex();
 		do_authentication(authctxt);
@@ -2336,6 +2390,23 @@ do_ssh1_kex(void)
 	packet_write_wait();
 }
 
+static void
+kex_server_sign(Key *privkey, Key *pubkey, u_char **signature, u_int *slen,
+    u_char *data, u_int dlen)
+{
+	if (privkey) {
+		if (PRIVSEP(key_sign(privkey, signature, slen, data, dlen) < 0))
+			fatal("%s: key_sign failed", __func__);
+	} else if (use_privsep) {
+		if (mm_key_sign(pubkey, signature, slen, data, dlen) < 0)
+			fatal("%s: pubkey_sign failed", __func__);
+	} else {
+		if (ssh_agent_sign(auth_conn, pubkey, signature, slen, data,
+		    dlen))
+			fatal("%s: ssh_agent_sign failed", __func__);
+	}
+}
+
 /*
  * SSH2 key exchange: diffie-hellman-group1-sha1
  */
@@ -2386,6 +2457,7 @@ do_ssh2_kex(void)
 	kex->load_host_public_key=&get_hostkey_public_by_type;
 	kex->load_host_private_key=&get_hostkey_private_by_type;
 	kex->host_key_index=&get_hostkey_index;
+	kex->sign = kex_server_sign;
 
 	xxx_kex = kex;
 



More information about the openssh-unix-dev mailing list