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