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@xxxxxxxxxxxxxxxxxxxxx> --- 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 _______________________________________________ openssh-unix-dev mailing list openssh-unix-dev@xxxxxxxxxxx https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev