[prev in list] [next in list] [prev in thread] [next in thread] 

List:       openssh-unix-dev
Subject:    PATCH: Support for encrypted host keys
From:       Zev Weiss <zevweiss () gmail ! com>
Date:       2012-01-28 9:25:57
Message-ID: 30D9F5B0-59F6-420E-AC97-FF6FD09C0339 () gmail ! com
[Download RAW message or body]

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@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;

_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic