Hello,
Would it be possible to add generic engine support to OpenSSH? One use in
particular would be to support TCP forwarding for secure mail server connections
and similar applications. This would permit an administrator to configure in an
arbitrary external engine to establish a secure RSA-based tunnel. OpenSSH would
need no information built into it to accomodate any particular engine.
One approach to this would be that taken by Stunnel (http://www.stunnel.org/).
Stunnel first loads the internal engine 'dynamic'. It then uses
ENGINE_load_builtin_engines(), but I think ENGINE_load_dynamic() would be more
efficient. Next a series of ENGINE_ctrl_cmd_string() and other engine(1) calls
are used to locate, name, load and initialize an external engine. IMHO, they
did a very nice job of achieving a highly versatile implementation. It was easy
to configure in the particular engine I use.
Another approach would be to take advantage of the OpenSSL config(5)
configuration file mechanism. I don't know of any good examples of this out
on the web, but I coded up an implementation that works fine with OpenSSL 0.9.7
or 0.9.8 distributions on HP-UX, see following. I should also mention the
Stunnel approach works fine with 0.9.7 or 0.9.8 OpenSSL. I only coded this for
sshd, but it would be very much the same for ssh. This takes fewer config file
directives and is less difficult to implement than the stunnel approach, but
does introduce the additional OpenSSL configuration file.
Regards, Rich
HP-UX Security
=======================================Code changes for an engine-enabled sshd.
=======================================In this example,
CONF_modules_load_file(3) is used to implement engine loading into sshd. This
will require three additional configuration parameters to be parsed, as well as
the introduction of an OpenSSL CONF-formatted file for the directives, see
config(5) for format details.
===============================================Added to sshd_config for generic
engine loading:
------------------------------------------------
# Identify the RSA key to be loaded by the engine
EngineHostKey /usr/local/etc/enginekeyblob
# Give location of the OpenSSL CONF file
EngineConfigFile /usr/local/etc/server.cnf
# Identify section for engine directives within the engine CONF file
EngineConfigStanza server_conf
----------------------------------------------------------
==============================================A typical OpenSSL CONF file for
loading an external engine
---------------------------------------------
# Filename: /usr/local/etc/server.cnf
server_conf = server_def
[ server_def ]
engines = server_engines
[ server_engines ]
myengine = engine_section
[ engine_section ]
# Rename the engine to whatever, if necessary
engine_id = myengine
# external engine location
dynamic_path = /opt/openssl/lib/hpux32/engines/libmyengine.so
#default_algorithms = RAND,RSA
default_algorithms = ALL
# Load and initialize the engine
init = 1
----------------------------------
==========================================Add to 'struct ServerOptions'
in servconf.h:
--------------------------------------------
50a68,71> int engineindex; /*engine index in host_key_files */
> char *engconffile; /*engine config information */
> char *engconfstanza; /* engine config stanza */
>
--------------------------------------------
==================Add to servconf.c:
--------------------------------------------
74a67,69> options->engineindex = -1;
> options->engconffile = NULL;
> options->engconfstanza = NULL;
188a193,200> if (options->engineindex != -1) {
> /* Set defaults for configuring an OpenSSL engine */
> if (options->engconffile == NULL)
> options->engconffile = _PATH_OPENSSL_CONFIG;
>
> if (options->engconfstanza == NULL)
> options->engconfstanza = OPENSSL_STANZA;
> }
409a471,473> { "enginehostkey", sEngineHostKey, SSHCFG_GLOBAL },
> { "engineconfigfile", sEngineConfigFile, SSHCFG_GLOBAL },
> { "engineconfigstanza", sEngineConfigStanza, SSHCFG_GLOBAL },
915a995,1021> case sEngineHostKey:
> if (options->engineindex != -1) {
> fatal("%s line %d: One engine key allowed",
> filename, linenum);
> }
> options->engineindex = options->num_host_key_files;
> intptr = &options->num_host_key_files;
> if (*intptr >= MAX_HOSTKEYS)
> fatal("%s line %d: too many keys (max %d).",
> filename, linenum, MAX_HOSTKEYS);
> charptr = &options->host_key_files[*intptr];
> goto parse_filename;
>
> case sEngineConfigFile:
> /* default set in fill_default_server_options */
> charptr = &options->engconffile;
> goto parse_filename;
>
> case sEngineConfigStanza:
> /* default set in fill_default_server_options */
> charptr = &options->engconfstanza;
> arg = strdelim(&cp);
> if (!arg || *arg == '\0')
> fatal("%s line %d: missing stanza",
> filename, linenum);
> break;
>
---------------------------------------------
============================================key_load_engine_private() added to
authfile.c:
---------------------------------------------
48a49,50> #include <openssl/conf.h>
> #include <openssl/engine.h>
611a614,679> return prv;
> }
>
> /* Arguments passphrase and commentp are not used */
> Key *
> key_load_engine_private(char *engkey, const char *conffile,
> const char* stanza, const char *passphrase, char **commentp)
> {
> ENGINE *eng = NULL;
> EVP_PKEY *pk = NULL;
> Key *prv = NULL;
> char *name = "<no key>";
>
> // Load the OpenSSL internal engine called 'dynamic'
> ENGINE_load_dynamic();
>
> // Add the OpenSSL ENGINE configuration module
> OPENSSL_load_builtin_modules();
>
> // Identify the file and stanza for engine directives
> if (CONF_modules_load_file(conffile, stanza, 0) <= 0) {
> ERR_print_errors_fp(stderr);
> error("Auto configuration failed");
> goto finish;
> }
>
> // Fetch the external engine handle
> eng = ENGINE_get_last();
> if (!eng) {
> /* the engine isn't available */
> ERR_print_errors_fp(stderr);
> error("ENGINE_get_last failed.");
> goto finish;
> }
>
> // Fetch and store the private key through the engine
> pk = ENGINE_load_private_key(eng, engkey, NULL, (void
*)passphrase);
> if (pk == NULL) {
> ERR_print_errors_fp(stderr);
> debug("ENGINE_load_private_key failed");
> (void)ERR_get_error();
> goto finish;
> } else if (pk->type == EVP_PKEY_RSA) {
> prv = key_new(KEY_UNSPEC);
> prv->rsa = EVP_PKEY_get1_RSA(pk);
> prv->type = KEY_RSA;
> name = "rsa w/o comment";
> #ifdef DEBUG_PK
> RSA_print_fp(stderr, prv->rsa, 8);
> #endif
> if (RSA_blinding_on(prv->rsa, NULL) != 1) {
> ERR_print_errors_fp(stderr);
> error("key_load_eng_prv: RSA_blinding failed");
> key_free(prv);
> prv = NULL;
> }
> } else {
> error("key_load_engine_private: key type wrong");
> }
>
> finish: if (pk != NULL)
> EVP_PKEY_free(pk);
> if (prv != NULL && commentp)
> *commentp = xstrdup(name);
> debug("key_load_engine_private() done: type %s",
> prv ? key_type(prv) : "<unknown>");
-------------------------------------------------
=================================================Call to
key_load_engine_private() added to sshd.c:
--------------------------------------------------
1521a1546,1551> if (i == options.engineindex) {
> /* Multiple RSA2 keys allowed, but only one used */
> key = key_load_engine_private(options.host_key_files[i],
> options.engconffile, options.engconfstanza, NULL, NULL);
> debug("engine key load attempted, index: #%d", i);
> } else {
1522a1553> }
-------------------------------------------------