[RFC 1/2] Add support for openssl engine based keys

James Bottomley James.Bottomley at HansenPartnership.com
Thu Oct 26 18:43:14 AEDT 2017


Engine keys are keys whose file format is understood by a specific
engine rather than by openssl itself.  Since these keys are file
based, the pkcs11 interface isn't appropriate for them because they
don't actually represent tokens.

Signed-off-by: James Bottomley <James.Bottomley at HansenPartnership.com>
---
 Makefile.in  |   4 +-
 authfd.c     |  45 +++++++++++++++++
 authfd.h     |   7 +++
 ssh-add.c    |  35 ++++++++++++--
 ssh-agent.c  |  82 +++++++++++++++++++++++++++++++
 ssh-engine.c | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 ssh-engine.h |  10 ++++
 7 files changed, 332 insertions(+), 5 deletions(-)
 create mode 100644 ssh-engine.c
 create mode 100644 ssh-engine.h

diff --git a/Makefile.in b/Makefile.in
index c52ce191f..42725fcbd 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -172,8 +172,8 @@ scp$(EXEEXT): $(LIBCOMPAT) libssh.a scp.o progressmeter.o
 ssh-add$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-add.o
 	$(LD) -o $@ ssh-add.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
 
-ssh-agent$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-agent.o ssh-pkcs11-client.o
-	$(LD) -o $@ ssh-agent.o ssh-pkcs11-client.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
+ssh-agent$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-agent.o ssh-pkcs11-client.o ssh-engine.o
+	$(LD) -o $@ ssh-agent.o ssh-pkcs11-client.o ssh-engine.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
 
 ssh-keygen$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keygen.o
 	$(LD) -o $@ ssh-keygen.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
diff --git a/authfd.c b/authfd.c
index a460fa350..0a0fcfafc 100644
--- a/authfd.c
+++ b/authfd.c
@@ -514,6 +514,51 @@ ssh_remove_identity(int sock, struct sshkey *key)
 }
 
 /*
+ * Add an engine based identity
+ */
+int
+ssh_add_engine_key(int sock, const char *file, const char *engine,
+		   const char *pin, u_int lifetime, u_int confirm)
+{
+	struct sshbuf *msg;
+	int r, constrained = (lifetime || confirm);
+	u_char type = constrained ? SSH_AGENTC_ADD_ENGINE_KEY_CONSTRAINED :
+				    SSH_AGENTC_ADD_ENGINE_KEY;
+
+	msg = sshbuf_new();
+	if (!msg)
+		return SSH_ERR_ALLOC_FAIL;
+	r = sshbuf_put_u8(msg, type);
+	if (r)
+		goto out;
+	r = sshbuf_put_cstring(msg, engine);
+	if (r)
+		goto out;
+	r = sshbuf_put_cstring(msg, file);
+	if (r)
+		goto out;
+	r = sshbuf_put_cstring(msg, pin);
+	if (r)
+		goto out;
+	if (constrained) {
+		r = encode_constraints(msg, lifetime, confirm);
+		if (r)
+			goto out;
+	}
+	r = ssh_request_reply(sock, msg, msg);
+	if (r)
+		goto out;
+	r = sshbuf_get_u8(msg, &type);
+	if (r)
+		goto out;
+	r = (signed char)type;
+ out:
+	sshbuf_free(msg);
+	return r;
+}
+
+
+/*
  * Add/remove an token-based identity from the authentication server.
  * This call is intended only for use by ssh-add(1) and like applications.
  */
diff --git a/authfd.h b/authfd.h
index 43abf85da..aa433644a 100644
--- a/authfd.h
+++ b/authfd.h
@@ -36,6 +36,9 @@ int	ssh_update_card(int sock, int add, const char *reader_id,
 	    const char *pin, u_int life, u_int confirm);
 int	ssh_remove_all_identities(int sock, int version);
 
+int	ssh_add_engine_key(int sock, const char *file, const char *engine,
+			   const char *pin, u_int lifetime, u_int confirm);
+
 int	ssh_decrypt_challenge(int sock, struct sshkey* key, BIGNUM *challenge,
 	    u_char session_id[16], u_char response[16]);
 int	ssh_agent_sign(int sock, const struct sshkey *key,
@@ -75,6 +78,10 @@ int	ssh_agent_sign(int sock, const struct sshkey *key,
 #define SSH2_AGENTC_ADD_ID_CONSTRAINED		25
 #define SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED 26
 
+/* engine keys */
+#define SSH_AGENTC_ADD_ENGINE_KEY		27
+#define SSH_AGENTC_ADD_ENGINE_KEY_CONSTRAINED	28
+
 #define	SSH_AGENT_CONSTRAIN_LIFETIME		1
 #define	SSH_AGENT_CONSTRAIN_CONFIRM		2
 
diff --git a/ssh-add.c b/ssh-add.c
index 2afd48330..d85bcc7ec 100644
--- a/ssh-add.c
+++ b/ssh-add.c
@@ -337,6 +337,27 @@ add_file(int agent_fd, const char *filename, int key_only, int qflag)
 }
 
 static int
+add_engine_key(int agent_fd, const char *file, const char *engine)
+{
+	int ret;
+	char *pin = NULL;
+
+	ret = ssh_add_engine_key(agent_fd, file, engine, NULL, lifetime, confirm);
+	if (ret == SSH_ERR_KEY_WRONG_PASSPHRASE) {
+		pin = read_passphrase("Enter engine key passphrase:", RP_ALLOW_STDIN);
+		if (!pin)
+			return -1;
+		ret = ssh_add_engine_key(agent_fd, file, engine, pin, lifetime, confirm);
+	}
+	if (ret != SSH_AGENT_SUCCESS) {
+		fprintf(stderr, "failed to add engine key: %s\n", ssh_err(ret));
+	}
+	if (pin)
+		free (pin);
+	return ret;
+}
+
+static int
 update_card(int agent_fd, int add, const char *id)
 {
 	char *pin = NULL;
@@ -462,6 +483,7 @@ usage(void)
 	fprintf(stderr, "  -s pkcs11   Add keys from PKCS#11 provider.\n");
 	fprintf(stderr, "  -e pkcs11   Remove keys provided by PKCS#11 provider.\n");
 	fprintf(stderr, "  -q          Be quiet after a successful operation.\n");
+	fprintf(stderr, "  -o engine   key file is to be processed by specified openssl engine\n");
 }
 
 int
@@ -470,7 +492,7 @@ main(int argc, char **argv)
 	extern char *optarg;
 	extern int optind;
 	int agent_fd;
-	char *pkcs11provider = NULL;
+	char *pkcs11provider = NULL, *opensslengine = NULL;
 	int r, i, ch, deleting = 0, ret = 0, key_only = 0;
 	int xflag = 0, lflag = 0, Dflag = 0, qflag = 0;
 
@@ -500,7 +522,7 @@ main(int argc, char **argv)
 		exit(2);
 	}
 
-	while ((ch = getopt(argc, argv, "klLcdDxXE:e:qs:t:")) != -1) {
+	while ((ch = getopt(argc, argv, "klLcdDxXE:e:qs:t:o:")) != -1) {
 		switch (ch) {
 		case 'E':
 			fingerprint_hash = ssh_digest_alg_by_name(optarg);
@@ -548,6 +570,9 @@ main(int argc, char **argv)
 		case 'q':
 			qflag = 1;
 			break;
+		case 'o':
+			opensslengine = optarg;
+			break;
 		default:
 			usage();
 			ret = 1;
@@ -573,7 +598,11 @@ main(int argc, char **argv)
 
 	argc -= optind;
 	argv += optind;
-	if (pkcs11provider != NULL) {
+	if (opensslengine != NULL) {
+		if (add_engine_key(agent_fd, argv[0], opensslengine) < 0)
+		    ret = 1;
+		goto done;
+	} else if (pkcs11provider != NULL) {
 		if (update_card(agent_fd, !deleting, pkcs11provider) == -1)
 			ret = 1;
 		goto done;
diff --git a/ssh-agent.c b/ssh-agent.c
index 0c6c36592..6b8834737 100644
--- a/ssh-agent.c
+++ b/ssh-agent.c
@@ -85,11 +85,16 @@
 #include "digest.h"
 #include "ssherr.h"
 #include "match.h"
+#include "authfile.h"
 
 #ifdef ENABLE_PKCS11
 #include "ssh-pkcs11.h"
 #endif
 
+#ifdef USE_OPENSSL_ENGINE
+#include "ssh-engine.h"
+#endif
+
 #ifndef DEFAULT_PKCS11_WHITELIST
 # define DEFAULT_PKCS11_WHITELIST "/usr/lib*/*,/usr/local/lib*/*"
 #endif
@@ -525,6 +530,77 @@ no_identities(SocketEntry *e)
 	sshbuf_free(msg);
 }
 
+#ifdef USE_OPENSSL_ENGINE
+static void
+process_add_engine_key(SocketEntry *e)
+{
+	char *engine, *pin, *file, *comment;
+	int r, confirm = 0;
+	u_int seconds;
+	time_t death = 0;
+	u_char type;
+	struct sshkey *k, *kp;
+	Identity *id;
+
+	if ((r = sshbuf_get_cstring(e->request, &engine, NULL)) != 0 ||
+	    (r = sshbuf_get_cstring(e->request, &file, NULL)) != 0 ||
+	    (r = sshbuf_get_cstring(e->request, &pin, NULL)) != 0)
+		fatal("%s: buffer error: %s", __func__, ssh_err(r));
+
+	while (sshbuf_len(e->request)) {
+		if ((r = sshbuf_get_u8(e->request, &type)) != 0)
+			fatal("%s: buffer error: %s", __func__, ssh_err(r));
+		switch (type) {
+		case SSH_AGENT_CONSTRAIN_LIFETIME:
+			if ((r = sshbuf_get_u32(e->request, &seconds)) != 0)
+				fatal("%s: buffer error: %s",
+				    __func__, ssh_err(r));
+			death = monotime() + seconds;
+			break;
+		case SSH_AGENT_CONSTRAIN_CONFIRM:
+			confirm = 1;
+			break;
+		default:
+			error("%s: Unknown constraint type %d", __func__, type);
+			goto send;
+		}
+	}
+	if (lifetime && !death)
+		death = monotime() + lifetime;
+
+	if ((r = engine_process_add(engine, file, pin, &k)) < 0)
+		goto send;
+
+	if (sshkey_load_public(file, &kp, &comment) < 0)
+		comment = xstrdup(file);
+	else
+		sshkey_free(kp);
+
+	if (lookup_identity(k) == NULL) {
+		id = xcalloc(1, sizeof(Identity));
+		id->key = k;
+		id->provider = xstrdup(engine);
+		id->comment = comment;
+		id->death = death;
+		id->confirm = confirm;
+		TAILQ_INSERT_TAIL(&idtab->idlist, id, next);
+		idtab->nentries++;
+		r = SSH_AGENT_SUCCESS;
+	} else {
+		sshkey_free(k);
+	}
+
+send:
+	free(pin);
+	free(engine);
+	free(file);
+	/* open code send_status because need to return actual error */
+	if (sshbuf_put_u32(e->output, 1) != 0 ||
+	    sshbuf_put_u8(e->output, r) != 0)
+		fatal("%s: buffer error", __func__);
+}
+#endif	/* USE_OPENSSL_ENGINE */
+
 #ifdef ENABLE_PKCS11
 static void
 process_add_smartcard_key(SocketEntry *e)
@@ -730,6 +806,12 @@ process_message(u_int socknum)
 		process_remove_smartcard_key(e);
 		break;
 #endif /* ENABLE_PKCS11 */
+#ifdef USE_OPENSSL_ENGINE
+	case SSH_AGENTC_ADD_ENGINE_KEY:
+	case SSH_AGENTC_ADD_ENGINE_KEY_CONSTRAINED:
+		process_add_engine_key(e);
+		break;
+#endif	/* USE_OPENSSL_ENGINE */
 	default:
 		/* Unknown message.  Respond with failure. */
 		error("Unknown message %d", type);
diff --git a/ssh-engine.c b/ssh-engine.c
new file mode 100644
index 000000000..f4673e4ee
--- /dev/null
+++ b/ssh-engine.c
@@ -0,0 +1,154 @@
+#include "includes.h"
+
+#include <string.h>
+
+#include <openssl/engine.h>
+#include <openssl/evp.h>
+#include <openssl/ui.h>
+
+#include "log.h"
+#include "ssh-engine.h"
+#include "sshkey.h"
+#include "ssherr.h"
+#include "xmalloc.h"
+
+struct ui_data {
+	char *passphrase;
+	int ret;
+};
+
+static int
+ui_read(UI *ui, UI_STRING *uis)
+{
+	struct ui_data *d = UI_get0_user_data(ui);
+	d->ret = 0;
+
+	if (UI_get_string_type(uis) == UIT_PROMPT) {
+		if (d->passphrase == NULL || d->passphrase[0] == '\0') {
+			/* we sent no passphrase but get asked for one
+			 * send an interrupt event to avoid DA implications */
+			d->ret = -2;
+		} else {
+			UI_set_result(ui, uis, d->passphrase);
+			d->ret = 1;
+		}
+	}
+
+	return d->ret;
+}
+
+int
+engine_process_add(char *engine, char *file, char *pin,
+		   struct sshkey **k)
+{
+	EVP_PKEY *pk;
+	ENGINE *e;
+	struct sshkey *key;
+	int ret;
+	UI_METHOD *ui;
+	EVP_PKEY_CTX *ctx;
+	char hash[SHA256_DIGEST_LENGTH], result[1024];
+	size_t siglen;
+	struct ui_data d;
+
+	verbose("%s: add provider=%s, key=%s", __func__, engine, file);
+
+	ret = SSH_ERR_INTERNAL_ERROR;
+	e = ENGINE_by_id(engine);
+	if (!e) {
+		verbose("%s: failed to get engine %s", __func__, engine);
+		ERR_print_errors_fp(stderr);
+		return ret;
+	}
+
+	ui = UI_create_method("ssh-agent password writer");
+	if (!ui) {
+		verbose("%s: failed to create UI method", __func__);
+		ERR_print_errors_fp(stderr);
+		return ret;
+	}
+	UI_method_set_reader(ui, ui_read);
+
+	if (!ENGINE_init(e)) {
+		verbose("%s: failed to init engine %s", __func__, engine);
+		ERR_print_errors_fp(stderr);
+		return ret;
+	}
+
+	d.passphrase = pin;
+	pk = ENGINE_load_private_key(e, file, ui, &d);
+	ENGINE_finish(e);
+
+	if (d.ret == -2)
+		return SSH_ERR_KEY_WRONG_PASSPHRASE;
+
+	if (!pk) {
+		verbose("%s: engine returned no key", __func__);
+		ERR_print_errors_fp(stderr);
+		return ret;
+	}
+
+	/* here's a nasty problem: most engines don't tell us the password
+	 * was wrong until we try to use the key, so do a test to see */
+	ctx = EVP_PKEY_CTX_new(pk, NULL);
+	if (!ctx) {
+		verbose("%s: openssl context allocation failed", __func__);
+		ERR_print_errors_fp(stderr);
+		goto err_free_pkey;
+	}
+
+	EVP_PKEY_sign_init(ctx);
+
+	siglen=sizeof(result);
+	ret = EVP_PKEY_sign(ctx, result, &siglen, hash, sizeof(hash));
+	EVP_PKEY_CTX_free(ctx);
+
+	if (ret != 1) {
+		verbose("%s: trial signature failed with %d", __func__, ret);
+		ERR_print_errors_fp(stderr);
+		ret = SSH_ERR_KEY_WRONG_PASSPHRASE;
+		goto err_free_pkey;
+	}
+
+	ret = SSH_ERR_ALLOC_FAIL;
+
+	key = sshkey_new(KEY_UNSPEC);
+	key->flags |= SSHKEY_FLAG_EXT;
+	if (!key)
+		goto err_free_pkey;
+
+	switch (EVP_PKEY_id(pk)) {
+	case EVP_PKEY_RSA:
+		key->type = KEY_RSA;
+		key->rsa = EVP_PKEY_get1_RSA(pk);
+		break;
+	case EVP_PKEY_DSA:
+		key->type = KEY_DSA;
+		key->dsa = EVP_PKEY_get1_DSA(pk);
+		break;
+#ifdef OPENSSL_HAS_ECC
+	case EVP_PKEY_EC:
+		key->type = KEY_ECDSA;
+		key->ecdsa = EVP_PKEY_get1_EC_KEY(pk);
+		key->ecdsa_nid = sshkey_ecdsa_key_to_nid(key->ecdsa);
+		if (key->ecdsa_nid == -1 ||
+		    sshkey_curve_nid_to_name(key->ecdsa_nid) == NULL)
+			goto err_free_sshkey;
+		break;
+#endif
+	default:
+		verbose("%s: Unrecognised key type %d\n", __func__, EVP_PKEY_id(pk));
+		ret = SSH_ERR_INVALID_FORMAT;
+		goto err_free_sshkey;
+	}
+	*k = key;
+	key = NULL;
+	ret = 1;
+ err_free_sshkey:
+	if (key)
+		sshkey_free(key);
+ err_free_pkey:
+	EVP_PKEY_free(pk);
+	verbose("%s: returning %d", __func__, ret);
+	return ret;
+}
diff --git a/ssh-engine.h b/ssh-engine.h
new file mode 100644
index 000000000..b5e85f44d
--- /dev/null
+++ b/ssh-engine.h
@@ -0,0 +1,10 @@
+#ifndef _ENGINE_H
+#define _ENGINE_H
+
+#include "sshkey.h"
+
+int
+engine_process_add(char *engine, char *file, char *pin,
+		   struct sshkey **k);
+
+#endif
-- 
2.12.3


More information about the openssh-unix-dev mailing list