PATCH: Support for encrypted host keys

Zev Weiss zevweiss at gmail.com
Sat Jan 28 20:25:57 EST 2012


Hello all,

I recently found myself wanting to run sshd with passphrase-protected host keys rather than the usual unencrypted format, and was somewhat surprised to discover that sshd did not support this.  I'm not sure if there's any particular reason for that, but I've developed the below patch (relative to current CVS at time of writing) that implements this.  It prompts for the passphrase when the daemon is started, similarly to Apache's behavior with encrypted SSL certificates.

My initial implementation instead operated by passing the passphrase along to the rexec child, but I decided I thought it was slightly nicer to decrypt the key once and pass it along rather than redoing it every time.  I can send the previous version if that would be preferred though -- this key-passing version does have some resulting ugliness in its handling of options.num_host_key_files, as described in a comment in the patch.


Thanks,
Zev Weiss
--

 Makefile.in |    2 +-
 buffer.h    |    5 ++
 bufkey.c    |  132 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 sshd.c      |  141 ++++++++++++++++++++++++++++++++++++++++++++++++-----------
 4 files changed, 253 insertions(+), 27 deletions(-)

diff --git a/Makefile.in b/Makefile.in
index 3be3aa6..3b47d18 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -61,7 +61,7 @@ MANFMT=@MANFMT@
 
 TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-pkcs11-helper$(EXEEXT) ssh-agent$(EXEEXT) scp$(EXEEXT) sftp-server$(EXEEXT) sftp$(EXEEXT)
 
-LIBSSH_OBJS=acss.o authfd.o authfile.o bufaux.o bufbn.o buffer.o \
+LIBSSH_OBJS=acss.o authfd.o authfile.o bufaux.o bufbn.o bufkey.o buffer.o \
 	canohost.o channels.o cipher.o cipher-acss.o cipher-aes.o \
 	cipher-bf1.o cipher-ctr.o cipher-3des1.o cleanup.o \
 	compat.o compress.o crc32.o deattack.o fatal.o hostfile.o \
diff --git a/buffer.h b/buffer.h
index e2a9dd1..a0c62c1 100644
--- a/buffer.h
+++ b/buffer.h
@@ -86,6 +86,11 @@ char	*buffer_get_cstring_ret(Buffer *, u_int *);
 void	*buffer_get_string_ptr_ret(Buffer *, u_int *);
 int	buffer_get_char_ret(char *, Buffer *);
 
+#include "key.h"
+
+void	buffer_put_key(Buffer *buffer, const Key *key);
+Key	*buffer_get_key(Buffer *buffer);
+
 #ifdef OPENSSL_HAS_ECC
 #include <openssl/ec.h>
 
diff --git a/bufkey.c b/bufkey.c
new file mode 100644
index 0000000..85a0c35
--- /dev/null
+++ b/bufkey.c
@@ -0,0 +1,132 @@
+/*
+ * Author: Zev Weiss <zevweiss at gmail.com>
+ *
+ * Functions for storing and retrieving Key structs into/from Buffers.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include "includes.h"
+
+#include <sys/types.h>
+
+#include <openssl/bn.h>
+
+#include "xmalloc.h"
+#include "buffer.h"
+#include "log.h"
+#include "key.h"
+#include "rsa.h"
+
+static void
+buffer_put_key_rsa(Buffer *buffer, const RSA *key)
+{
+	buffer_put_bignum(buffer, key->e);
+	buffer_put_bignum(buffer, key->n);
+	buffer_put_bignum(buffer, key->d);
+	buffer_put_bignum(buffer, key->iqmp);
+	buffer_put_bignum(buffer, key->p);
+	buffer_put_bignum(buffer, key->q);
+}
+
+static void
+buffer_get_key_rsa(Buffer *buffer, RSA *key)
+{
+	buffer_get_bignum(buffer, key->e);
+	buffer_get_bignum(buffer, key->n);
+	buffer_get_bignum(buffer, key->d);
+	buffer_get_bignum(buffer, key->iqmp);
+	buffer_get_bignum(buffer, key->p);
+	buffer_get_bignum(buffer, key->q);
+	rsa_generate_additional_parameters(key);
+}
+
+static void
+buffer_put_key_dsa(Buffer *buffer, const DSA *key)
+{
+	buffer_put_bignum(buffer, key->p);
+	buffer_put_bignum(buffer, key->q);
+	buffer_put_bignum(buffer, key->g);
+	buffer_put_bignum(buffer, key->pub_key);
+	buffer_put_bignum(buffer, key->priv_key);
+}
+
+static void
+buffer_get_key_dsa(Buffer *buffer, DSA *key)
+{
+	buffer_get_bignum(buffer, key->p);
+	buffer_get_bignum(buffer, key->q);
+	buffer_get_bignum(buffer, key->g);
+	buffer_get_bignum(buffer, key->pub_key);
+	buffer_get_bignum(buffer, key->priv_key);
+}
+
+void
+buffer_put_key(Buffer *buffer, const Key *key)
+{
+	if (key->cert != NULL || key->ecdsa != NULL || key->ecdsa_nid != -1)
+		fatal("%s: unsupported key feature", __func__);
+
+	buffer_put_int(buffer, key->type);
+	buffer_put_int(buffer, key->flags);
+
+	switch (key->type) {
+	case KEY_RSA1:
+	case KEY_RSA:
+		buffer_put_key_rsa(buffer, key->rsa);
+		break;
+	case KEY_DSA:
+		buffer_put_key_dsa(buffer, key->dsa);
+		break;
+	default:
+		fatal("%s: unsupported key type (%s)", __func__,
+		    key_type(key));
+	}
+}
+
+Key *
+buffer_get_key(Buffer *buffer)
+{
+	Key *key;
+	int type, flags;
+
+	type = buffer_get_int(buffer);
+	flags = buffer_get_int(buffer);
+
+	key = key_new_private(type);
+	key->flags = flags;
+
+	switch (type) {
+	case KEY_RSA1:
+	case KEY_RSA:
+		buffer_get_key_rsa(buffer, key->rsa);
+		break;
+	case KEY_DSA:
+		buffer_get_key_dsa(buffer, key->dsa);
+		break;
+	default:
+		fatal("%s: unsupported key type (%s)", __func__,
+		    key_type(key));
+	}
+
+	return key;
+}
diff --git a/sshd.c b/sshd.c
index c8d71f8..f458860 100644
--- a/sshd.c
+++ b/sshd.c
@@ -175,6 +175,7 @@ int rexeced_flag = 0;
 int rexec_flag = 1;
 int rexec_argc = 0;
 char **rexec_argv;
+int num_rexec_recvd_host_keys = 0;
 
 /*
  * The sockets that the server is listening; this is used in the SIGHUP
@@ -898,6 +899,7 @@ usage(void)
 static void
 send_rexec_state(int fd, Buffer *conf)
 {
+	int i, num_host_keys;
 	Buffer m;
 
 	debug3("%s: entering fd = %d config len %d", __func__, fd,
@@ -914,6 +916,8 @@ send_rexec_state(int fd, Buffer *conf)
 	 *	bignum	p			"
 	 *	bignum	q			"
 	 *	string rngseed		(only if OpenSSL is not self-seeded)
+	 *      u_int	num_host_keys
+	 *	Key	host_keys	num_host_keys times
 	 */
 	buffer_init(&m);
 	buffer_put_cstring(&m, buffer_ptr(conf));
@@ -934,6 +938,18 @@ send_rexec_state(int fd, Buffer *conf)
 	rexec_send_rng_seed(&m);
 #endif
 
+	num_host_keys = 0;
+	for (i = 0; i < options.num_host_key_files; i++) {
+		if (sensitive_data.host_keys[i] != NULL)
+			++num_host_keys;
+	}
+
+	buffer_put_int(&m, num_host_keys);
+	for (i = 0; i < options.num_host_key_files; i++) {
+		if (sensitive_data.host_keys[i] != NULL)
+			buffer_put_key(&m, sensitive_data.host_keys[i]);
+	}
+
 	if (ssh_msg_send(fd, 0, &m) == -1)
 		fatal("%s: ssh_msg_send failed", __func__);
 
@@ -946,8 +962,10 @@ static void
 recv_rexec_state(int fd, Buffer *conf)
 {
 	Buffer m;
+	Key *hk;
 	char *cp;
 	u_int len;
+	int i, num_host_keys;
 
 	debug3("%s: entering fd = %d", __func__, fd);
 
@@ -981,6 +999,30 @@ recv_rexec_state(int fd, Buffer *conf)
 	rexec_recv_rng_seed(&m);
 #endif
 
+	num_host_keys = buffer_get_int(&m);
+	debug("%s: receiving %d host keys", __func__, num_host_keys);
+	sensitive_data.host_keys = xcalloc(num_host_keys, sizeof(Key *));
+
+	num_rexec_recvd_host_keys = num_host_keys;
+
+	for (i = 0; i < num_host_keys; i++) {
+		hk = buffer_get_key(&m);
+		debug("%s: received %s host key", __func__, key_type(hk));
+		sensitive_data.host_keys[i] = hk;
+		switch (hk->type) {
+		case KEY_RSA1:
+			sensitive_data.ssh1_host_key = hk;
+			sensitive_data.have_ssh1_key = 1;
+			break;
+		case KEY_RSA:
+		case KEY_DSA:
+			sensitive_data.have_ssh2_key = 1;
+			break;
+		default:
+			fatal("%s: unsupported host key type", __func__);
+		}
+	}
+
 	buffer_free(&m);
 
 	debug3("%s: done", __func__);
@@ -1308,6 +1350,41 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s)
 	}
 }
 
+static Key *
+sshd_key_load_private(const char *filename)
+{
+	Key *key;
+	char prompt[300], *passphrase = "";
+	int quit, i;
+
+	key = key_load_private(filename, passphrase, NULL);
+
+	if (key == NULL) {
+		snprintf(prompt, sizeof prompt,
+		    "Enter passphrase for key '%.100s': ", filename);
+		/* options.number_of_password_prompts doesn't exist in sshd */
+		for (i = 0; i < 3; i++) {
+			passphrase = read_passphrase(prompt, 0);
+			if (strcmp(passphrase, "") != 0) {
+				key = key_load_private(filename, passphrase,
+				    NULL);
+				quit = 0;
+			} else {
+				debug2("no passphrase given, try next key");
+				quit = 1;
+			}
+			if (key != NULL || quit) {
+				memset(passphrase, 0, strlen(passphrase));
+				xfree(passphrase);
+				break;
+			}
+			memset(passphrase, 0, strlen(passphrase));
+			xfree(passphrase);
+			debug2("bad passphrase given, try again...");
+		}
+	}
+	return key;
+}
 
 /*
  * Main program for the daemon.
@@ -1550,6 +1627,15 @@ main(int ac, char **av)
 	parse_server_config(&options, rexeced_flag ? "rexec" : config_file_name,
 	    &cfg, NULL, NULL, NULL);
 
+	/*
+	 * parse_server_config() changes options.num_host_key_files,
+	 * but we need to change it back to what we received via
+	 * recv_rexec_state to accurately reflect the number of keys
+	 * in sensitive_data.host_keys.  This is a bit ugly.
+	 */
+	if (rexeced_flag)
+		options.num_host_key_files = num_rexec_recvd_host_keys;
+
 	seed_rng();
 
 	/* Fill in default values for those options not explicitly set. */
@@ -1583,35 +1669,38 @@ main(int ac, char **av)
 	}
 	endpwent();
 
-	/* load private 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_keys[i] = NULL;
-
-	for (i = 0; i < options.num_host_key_files; i++) {
-		key = key_load_private(options.host_key_files[i], "", NULL);
-		sensitive_data.host_keys[i] = key;
-		if (key == NULL) {
-			error("Could not load host key: %s",
-			    options.host_key_files[i]);
+	if (!rexeced_flag) {
+		/* load private 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_keys[i] = NULL;
-			continue;
-		}
-		switch (key->type) {
-		case KEY_RSA1:
-			sensitive_data.ssh1_host_key = key;
-			sensitive_data.have_ssh1_key = 1;
-			break;
-		case KEY_RSA:
-		case KEY_DSA:
-		case KEY_ECDSA:
-			sensitive_data.have_ssh2_key = 1;
-			break;
+
+		for (i = 0; i < options.num_host_key_files; i++) {
+			key = sshd_key_load_private(options.host_key_files[i]);
+			sensitive_data.host_keys[i] = key;
+			if (key == NULL) {
+				error("Could not load host key: %s",
+				    options.host_key_files[i]);
+				sensitive_data.host_keys[i] = NULL;
+				continue;
+			}
+			switch (key->type) {
+			case KEY_RSA1:
+				sensitive_data.ssh1_host_key = key;
+				sensitive_data.have_ssh1_key = 1;
+				break;
+			case KEY_RSA:
+			case KEY_DSA:
+			case KEY_ECDSA:
+				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, key->type,
-		    key_type(key));
 	}
+
 	if ((options.protocol & SSH_PROTO_1) && !sensitive_data.have_ssh1_key) {
 		logit("Disabling protocol version 1. Could not load host key");
 		options.protocol &= ~SSH_PROTO_1;



More information about the openssh-unix-dev mailing list