[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