HostKey in hardware?

Damien Miller djm at mindrot.org
Fri Nov 23 12:18:14 EST 2012


On Wed, 21 Nov 2012, andrew cooke wrote:

> 
> Hi,
> 
> Is there any way to store HostKey in hardware (and delegate the related
> processing)?

I've been wanting to do this for a while, but hadn't got around to it.

Congratulations, you managed to troll me into action :) (see below)

> The hardware I am using (Spyrus Lynks II) doesn't have PKCS#11 support, so I
> would prefer the OpenSSL route (since I already have an engine), but if
> necessary I would consider writing a minimal PKCS#11 implementation (can
> anyone give a rough idea of the amount of work involved to get HostKey
> working, only?)

We aren't likely to add support for anything other than PKCS#11 host keys.

Here's a (lightly tested) patch for PKCS#11 host keys. At the moment, the
keys are loaded using a fixed PIN of 0000, but there's probably a better
way to do it. I don't really want sshd to block at startup time while looking
for a password, but my PKCS#15-fu isn't good enough to know how to create
keys that don't require a PIN at all.

diff --git servconf.c servconf.c
index 9919778..3670a2f 100644
--- servconf.c
+++ servconf.c
@@ -67,6 +67,7 @@ initialize_server_options(ServerOptions *options)
 	options->listen_addrs = NULL;
 	options->address_family = -1;
 	options->num_host_key_files = 0;
+	options->num_host_key_pkcs11_providers = 0;
 	options->num_host_cert_files = 0;
 	options->pid_file = NULL;
 	options->server_key_bits = -1;
@@ -160,6 +161,7 @@ fill_default_server_options(ServerOptions *options)
 			    _PATH_HOST_ECDSA_KEY_FILE;
 		}
 	}
+	/* No PKCS#11 providers by default */
 	/* No certificates by default */
 	if (options->num_ports == 0)
 		options->ports[options->num_ports++] = SSH_DEFAULT_PORT;
@@ -281,7 +283,8 @@ fill_default_server_options(ServerOptions *options)
 /* Keyword tokens. */
 typedef enum {
 	sBadOption,		/* == unknown option */
-	sPort, sHostKeyFile, sServerKeyBits, sLoginGraceTime, sKeyRegenerationTime,
+	sPort, sHostKeyFile, sHostKeyPKCS11, sServerKeyBits,
+	sLoginGraceTime, sKeyRegenerationTime,
 	sPermitRootLogin, sLogFacility, sLogLevel,
 	sRhostsRSAAuthentication, sRSAAuthentication,
 	sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup,
@@ -324,6 +327,7 @@ static struct {
 	{ "port", sPort, SSHCFG_GLOBAL },
 	{ "hostkey", sHostKeyFile, SSHCFG_GLOBAL },
 	{ "hostdsakey", sHostKeyFile, SSHCFG_GLOBAL },		/* alias */
+	{ "hostkeypkcs11", sHostKeyPKCS11, SSHCFG_GLOBAL },
 	{ "pidfile", sPidFile, SSHCFG_GLOBAL },
 	{ "serverkeybits", sServerKeyBits, SSHCFG_GLOBAL },
 	{ "logingracetime", sLoginGraceTime, SSHCFG_GLOBAL },
@@ -907,22 +911,33 @@ process_server_config_line(ServerOptions *options, char *line,
 			fatal("%s line %d: missing file name.",
 			    filename, linenum);
 		if (*activep && *charptr == NULL) {
-			*charptr = derelativise_path(arg);
+			if (strcasecmp(arg, "none") == 0)
+				*charptr = xstrdup("none");
+			else
+				*charptr = derelativise_path(arg);
 			/* increase optional counter */
 			if (intptr != NULL)
 				*intptr = *intptr + 1;
 		}
 		break;
 
+	case sHostKeyPKCS11:
+		intptr = &options->num_host_key_pkcs11_providers;
+		if (*intptr >= MAX_HOSTPKCS11)
+			fatal("%s line %d: too many host key PKCS#11 providers "
+			    "specified (max %d).", filename, linenum,
+			    MAX_HOSTPKCS11);
+		charptr = &options->host_key_pkcs11_providers[*intptr];
+		goto parse_filename;
+
 	case sHostCertificate:
 		intptr = &options->num_host_cert_files;
-		if (*intptr >= MAX_HOSTKEYS)
+		if (*intptr >= MAX_HOSTCERTS)
 			fatal("%s line %d: too many host certificates "
 			    "specified (max %d).", filename, linenum,
 			    MAX_HOSTCERTS);
 		charptr = &options->host_cert_files[*intptr];
 		goto parse_filename;
-		break;
 
 	case sPidFile:
 		charptr = &options->pid_file;
@@ -1918,7 +1933,9 @@ dump_config(ServerOptions *o)
 	    o->authorized_keys_files);
 	dump_cfg_strarray(sHostKeyFile, o->num_host_key_files,
 	     o->host_key_files);
-	dump_cfg_strarray(sHostKeyFile, o->num_host_cert_files,
+	dump_cfg_strarray(sHostKeyPKCS11, o->num_host_key_pkcs11_providers,
+	    o->host_key_pkcs11_providers);
+	dump_cfg_strarray(sHostCertificate, o->num_host_cert_files,
 	     o->host_cert_files);
 	dump_cfg_strarray(sAllowUsers, o->num_allow_users, o->allow_users);
 	dump_cfg_strarray(sDenyUsers, o->num_deny_users, o->deny_users);
diff --git servconf.h servconf.h
index da2374b..e48b38a 100644
--- servconf.h
+++ servconf.h
@@ -23,8 +23,9 @@
 #define MAX_ALLOW_GROUPS	256	/* Max # groups on allow list. */
 #define MAX_DENY_GROUPS		256	/* Max # groups on deny list. */
 #define MAX_SUBSYSTEMS		256	/* Max # subsystems. */
-#define MAX_HOSTKEYS		256	/* Max # hostkeys. */
-#define MAX_HOSTCERTS		256	/* Max # host certificates. */
+#define MAX_HOSTKEYS		16	/* Max # hostkeys. */
+#define MAX_HOSTCERTS		16	/* Max # host certificates. */
+#define MAX_HOSTPKCS11		16	/* Max # host key PKCS#11 providers. */
 #define MAX_ACCEPT_ENV		256	/* Max # of env vars. */
 #define MAX_MATCH_GROUPS	256	/* Max # of groups for Match. */
 #define MAX_AUTHKEYS_FILES	256	/* Max # of authorized_keys files. */
@@ -55,10 +56,17 @@ typedef struct {
 	char   *listen_addr;		/* Address on which the server listens. */
 	struct addrinfo *listen_addrs;	/* Addresses on which the server listens. */
 	int     address_family;		/* Address family used by the server. */
-	char   *host_key_files[MAX_HOSTKEYS];	/* Files containing host keys. */
-	int     num_host_key_files;     /* Number of files for host keys. */
-	char   *host_cert_files[MAX_HOSTCERTS];	/* Files containing host certs. */
-	int     num_host_cert_files;     /* Number of files for host certs. */
+
+	/* Host key files */
+	char   *host_key_files[MAX_HOSTKEYS];
+	int     num_host_key_files;
+	/* Host key PKCS#11 providers */
+	char   *host_key_pkcs11_providers[MAX_HOSTPKCS11];
+	int     num_host_key_pkcs11_providers;
+	/* Host certificate files */
+	char   *host_cert_files[MAX_HOSTCERTS];
+	int     num_host_cert_files;
+
 	char   *pid_file;	/* Where to put our pid */
 	int     server_key_bits;/* Size of the server key. */
 	int     login_grace_time;	/* Disconnect if no auth in this time
diff --git sshd.c sshd.c
index d3f53c0..83eab58 100644
--- sshd.c
+++ sshd.c
@@ -105,6 +105,13 @@
 #include "ssh-sandbox.h"
 #include "version.h"
 
+#ifdef ENABLE_PKCS11
+#include "ssh-pkcs11.h"
+#endif
+
+/* PIN used for PKCS#11 providers */
+#define SSHD_PKCS11_PIN	"0000"	/* XXX */
+
 #ifdef LIBWRAP
 #include <tcpd.h>
 #include <syslog.h>
@@ -188,6 +195,7 @@ Kex *xxx_kex;
 struct {
 	Key	*server_key;		/* ephemeral server key */
 	Key	*ssh1_host_key;		/* ssh1 host key */
+	int	num_host_keys;		/* number of private host keys */
 	Key	**host_keys;		/* all private host keys */
 	Key	**host_certificates;	/* all public host certificates */
 	int	have_ssh1_key;
@@ -534,7 +542,7 @@ destroy_sensitive_data(void)
 		key_free(sensitive_data.server_key);
 		sensitive_data.server_key = NULL;
 	}
-	for (i = 0; i < options.num_host_key_files; i++) {
+	for (i = 0; i < sensitive_data.num_host_keys; i++) {
 		if (sensitive_data.host_keys[i]) {
 			key_free(sensitive_data.host_keys[i]);
 			sensitive_data.host_keys[i] = NULL;
@@ -561,7 +569,7 @@ demote_sensitive_data(void)
 		sensitive_data.server_key = tmp;
 	}
 
-	for (i = 0; i < options.num_host_key_files; i++) {
+	for (i = 0; i < sensitive_data.num_host_keys; i++) {
 		if (sensitive_data.host_keys[i]) {
 			tmp = key_demote(sensitive_data.host_keys[i]);
 			key_free(sensitive_data.host_keys[i]);
@@ -747,7 +755,7 @@ list_hostkey_types(void)
 	Key *key;
 
 	buffer_init(&b);
-	for (i = 0; i < options.num_host_key_files; i++) {
+	for (i = 0; i < sensitive_data.num_host_keys; i++) {
 		key = sensitive_data.host_keys[i];
 		if (key == NULL)
 			continue;
@@ -791,7 +799,7 @@ get_hostkey_by_type(int type, int need_private)
 	int i;
 	Key *key;
 
-	for (i = 0; i < options.num_host_key_files; i++) {
+	for (i = 0; i < sensitive_data.num_host_keys; i++) {
 		switch (type) {
 		case KEY_RSA_CERT_V00:
 		case KEY_DSA_CERT_V00:
@@ -826,7 +834,7 @@ get_hostkey_private_by_type(int type)
 Key *
 get_hostkey_by_index(int ind)
 {
-	if (ind < 0 || ind >= options.num_host_key_files)
+	if (ind < 0 || ind >= sensitive_data.num_host_keys)
 		return (NULL);
 	return (sensitive_data.host_keys[ind]);
 }
@@ -836,7 +844,7 @@ get_hostkey_index(Key *key)
 {
 	int i;
 
-	for (i = 0; i < options.num_host_key_files; i++) {
+	for (i = 0; i < sensitive_data.num_host_keys; i++) {
 		if (key_is_cert(key)) {
 			if (key == sensitive_data.host_certificates[i])
 				return (i);
@@ -1296,7 +1304,7 @@ main(int ac, char **av)
 {
 	extern char *optarg;
 	extern int optind;
-	int opt, i, j, on = 1;
+	int opt, i, j, nkeys, on = 1;
 	int sock_in = -1, sock_out = -1, newsock = -1;
 	const char *remote_ip;
 	int remote_port;
@@ -1305,7 +1313,7 @@ main(int ac, char **av)
 	u_int n;
 	u_int64_t ibytes, obytes;
 	mode_t new_umask;
-	Key *key;
+	Key *key, **keys;
 	Authctxt *authctxt;
 	struct connection_info *connection_info = get_connection_info(0, 0);
 
@@ -1530,20 +1538,54 @@ main(int ac, char **av)
 	debug("sshd version %.100s", SSH_VERSION);
 
 	/* 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;
+	sensitive_data.host_keys = xcalloc(options.num_host_key_files + 
+	    options.num_host_key_pkcs11_providers, sizeof(Key *));
+	sensitive_data.num_host_keys = 0;
+
+#ifdef ENABLE_PKCS11
+	if (options.num_host_key_pkcs11_providers > 0) {
+		if (pkcs11_init(0) != 0)
+			fatal("Could not initialise PKCS#11 for host keys");
+		for (i = 0; i < options.num_host_key_pkcs11_providers; i++) {
+			nkeys = pkcs11_add_provider(
+			    options.host_key_pkcs11_providers[i],
+			    SSHD_PKCS11_PIN, &keys);
+			if (nkeys == -1)
+				fatal("Failed to add PKCS#11 provider \"%s\"",
+				    options.host_key_pkcs11_providers[i]);
+			if (nkeys == 0) {
+				error("PKCS#11 provider \"%s\" yielded no keys",
+				    options.host_key_pkcs11_providers[i]);
+				continue;
+			}
+			for (j = 0; j < nkeys; j++) {
+				sensitive_data.host_keys[
+				    sensitive_data.num_host_keys++] = keys[j];
+			}
+			free(keys);
+		}
+	}
+#endif /* ENABLE_PKCS11 */
 
 	for (i = 0; i < options.num_host_key_files; i++) {
+		if (strcasecmp(options.host_key_files[i], "none") == 0)
+			continue;
 		key = key_load_private(options.host_key_files[i], "", NULL);
-		sensitive_data.host_keys[i] = key;
+		sensitive_data.host_keys[sensitive_data.num_host_keys++] = key;
 		if (key == NULL) {
 			error("Could not load host key: %s",
 			    options.host_key_files[i]);
-			sensitive_data.host_keys[i] = NULL;
 			continue;
 		}
+		debug("private host key: #%d type %d %s",
+		    sensitive_data.num_host_keys - 1, key->type, key_type(key));
+	}
+
+	/* Figure out whether we have loaded keys for protocols 1 and 2 */
+	for (i = 0; i < sensitive_data.num_host_keys; i++) {
+		key = sensitive_data.host_keys[i];
+		if (key == NULL)
+			continue;
 		switch (key->type) {
 		case KEY_RSA1:
 			sensitive_data.ssh1_host_key = key;
@@ -1555,9 +1597,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));
 	}
+
 	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;
@@ -1575,9 +1616,9 @@ main(int ac, char **av)
 	 * Load certificates. They are stored in an array at identical
 	 * indices to the public keys that they relate to.
 	 */
-	sensitive_data.host_certificates = xcalloc(options.num_host_key_files,
+	sensitive_data.host_certificates = xcalloc(sensitive_data.num_host_keys,
 	    sizeof(Key *));
-	for (i = 0; i < options.num_host_key_files; i++)
+	for (i = 0; i < sensitive_data.num_host_keys; i++)
 		sensitive_data.host_certificates[i] = NULL;
 
 	for (i = 0; i < options.num_host_cert_files; i++) {
@@ -1594,14 +1635,14 @@ main(int ac, char **av)
 			continue;
 		}
 		/* Find matching private key */
-		for (j = 0; j < options.num_host_key_files; j++) {
+		for (j = 0; j < sensitive_data.num_host_keys; j++) {
 			if (key_equal_public(key,
 			    sensitive_data.host_keys[j])) {
 				sensitive_data.host_certificates[j] = key;
 				break;
 			}
 		}
-		if (j >= options.num_host_key_files) {
+		if (j >= sensitive_data.num_host_keys) {
 			error("No matching private key for certificate: %s",
 			    options.host_cert_files[i]);
 			key_free(key);
diff --git sshd_config.5 sshd_config.5
index 91935d0..5ec06f2 100644
--- sshd_config.5
+++ sshd_config.5
@@ -517,6 +517,10 @@ for protocol version 1, and
 and
 .Pa /etc/ssh/ssh_host_rsa_key
 for protocol version 2.
+It is possible to skip loading of host keys by specifying a path of
+.Pa none
+in cases where keys are supplied via
+.Cm HostKeyPKCS11 .
 Note that
 .Xr sshd 8
 will refuse to use a file if it is group/world-accessible.
@@ -528,6 +532,15 @@ keys are used for version 1 and
 or
 .Dq rsa
 are used for version 2 of the SSH protocol.
+.It Cm HostKeyPKCS11
+Specify a PKCS#11 provider for host keys.
+.Nm
+will attempt to load all keys in this device assuming a PIN of
+.Dq 0000
+and use them as host keys.
+This option may be specified more than once to allow loading of host keys
+from multiple devices.
+The default is not to attempt to load host keys from PKCS#11 devices.
 .It Cm IgnoreRhosts
 Specifies that
 .Pa .rhosts


More information about the openssh-unix-dev mailing list