From 1e9262a6ff5a256ef884dcb6cb4c188f33721c7c Mon Sep 17 00:00:00 2001 From: Alex Wilson Date: Mon, 3 Aug 2015 16:27:44 -0700 Subject: [PATCH 19/34] PubKeyPlugin support This adds the PubKeyPlugin directive and associated code from SunSSH, allowing an in-process shared library to be called into to check public keys for authentication. diff -wpruN --no-dereference '--exclude=*.orig' a~/auth2-pubkey.c a/auth2-pubkey.c --- a~/auth2-pubkey.c 1970-01-01 00:00:00 +++ a/auth2-pubkey.c 1970-01-01 00:00:00 @@ -23,6 +23,11 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Copyright 2015 Joyent, Inc. + * Use is subject to license terms. + */ #include "includes.h" @@ -40,11 +45,13 @@ #include #include #include +#include #include "xmalloc.h" #include "ssh.h" #include "ssh2.h" #include "packet.h" +#include "digest.h" #include "kex.h" #include "sshbuf.h" #include "log.h" @@ -84,6 +91,15 @@ format_key(const struct sshkey *key) return ret; } +static const char *RSA_SYM_NAME = "sshd_user_rsa_key_allowed"; +static const char *ECDSA_SYM_NAME = "sshd_user_ecdsa_key_allowed"; +typedef int (*RSA_SYM)(struct passwd *, RSA *, const char *); +typedef int (*ECDSA_SYM)(struct passwd *, EC_KEY *, const char *); + +static const char *UNIV_SYM_NAME = "sshd_user_key_allowed"; +typedef int (*UNIV_SYM)(struct passwd *, const char *, + const u_char *, size_t); + static int userauth_pubkey(struct ssh *ssh, const char *method) { @@ -783,6 +799,135 @@ user_key_command_allowed2(struct passwd return found_key; } +/** + * Checks whether or not access is allowed based on a plugin specified + * in sshd_config (PubKeyPlugin). + * + * Note that this expects a symbol in the loaded library that takes + * the current user (pwd entry), the current RSA key and it's fingerprint. + * The symbol is expected to return 1 on success and 0 on failure. + * + * While we could optimize this code to dlopen once in the process' lifetime, + * sshd is already a slow beast, so this is really not a concern. + * The overhead is basically a rounding error compared to everything else, and + * it keeps this code minimally invasive. + */ +static int +user_key_allowed_from_plugin(struct passwd *pw, struct sshkey *key) +{ + RSA_SYM rsa_sym = NULL; + ECDSA_SYM ecdsa_sym = NULL; + UNIV_SYM univ_sym = NULL; + char *fp = NULL; + char *argfp = NULL; + void *handle = NULL; + int success = 0; + + if (options.pubkey_plugin == NULL || pw == NULL || key == NULL || + (key->type != KEY_RSA && + key->type != KEY_ECDSA)) + return success; + + handle = dlopen(options.pubkey_plugin, RTLD_NOW); + if (handle == NULL) { + debug("Unable to open library %s: %s", options.pubkey_plugin, + dlerror()); + goto out; + } + + /* + * If we have the new-style universal symbol for checking keys, use + * that instead of the old-style per-key-type symbols. + */ + univ_sym = (UNIV_SYM)dlsym(handle, UNIV_SYM_NAME); + if (univ_sym != NULL) { + u_char *blob; + const char *type = sshkey_type(key); + size_t len = 0; + if (sshkey_to_blob(key, &blob, &len) != 0) { + debug("failed to convert key to rfc4253 format"); + goto out; + } + debug("Invoking %s from %s", UNIV_SYM_NAME, + options.pubkey_plugin); + success = (*univ_sym)(pw, type, blob, len); + debug("pubkeyplugin returned: %d", success); + goto out; + } + + /* Otherwise, continue with the old-style fingerprint symbols. */ + fp = sshkey_fingerprint(key, SSH_DIGEST_MD5, SSH_FP_HEX); + if (fp == NULL) { + debug("failed to generate fingerprint"); + goto out; + } + if (strncmp(fp, "MD5:", 4) != 0) { + debug("fingerprint not in MD5:hex format"); + goto out; + } + /* give the plugin the string without leading MD5: */ + argfp = fp + 4; + + switch (key->type) { + case KEY_RSA: { + RSA *rsa; + + rsa_sym = (RSA_SYM)dlsym(handle, RSA_SYM_NAME); + if (rsa_sym == NULL) { + debug("Unable to resolve symbol %s: %s", RSA_SYM_NAME, + dlerror()); + goto out; + } + debug2("Invoking %s from %s", RSA_SYM_NAME, + options.pubkey_plugin); + rsa = EVP_PKEY_get1_RSA(key->pkey); + success = (*rsa_sym)(pw, rsa, argfp); + RSA_free(rsa); + break; + } + case KEY_ECDSA: { + EC_KEY *ec; + + ecdsa_sym = (ECDSA_SYM)dlsym(handle, ECDSA_SYM_NAME); + if (ecdsa_sym == NULL) { + debug("Unable to resolve symbol %s: %s", ECDSA_SYM_NAME, + dlerror()); + goto out; + } + debug2("Invoking %s from %s", ECDSA_SYM_NAME, + options.pubkey_plugin); + ec = EVP_PKEY_get1_EC_KEY(key->pkey); + success = (*ecdsa_sym)(pw, ec, argfp); + EC_KEY_free(ec); + break; + } + default: + debug2("user_key_plugins only support RSA and ECDSA keys"); + } + + debug("pubkeyplugin returned: %d", success); + +out: + if (handle != NULL) { + dlclose(handle); + ecdsa_sym = NULL; + rsa_sym = NULL; + univ_sym = NULL; + handle = NULL; + } + + if (success) + verbose("Found matching %s key: %s", sshkey_type(key), + fp != NULL ? fp : "NULL"); + + if (fp != NULL) { + free(fp); + fp = NULL; + } + + return success; +} + /* * Check whether key authenticates and authorises the user. */ @@ -857,6 +1002,10 @@ user_key_allowed(struct ssh *ssh, struct sshauthopt_free(opts); opts = NULL; + success = user_key_allowed_from_plugin(pw, key); + if (success > 0) + goto out; + if ((success = user_key_command_allowed2(pw, key, remote_ip, remote_host, conn_id, rdomain, &opts)) != 0) goto out; diff -wpruN --no-dereference '--exclude=*.orig' a~/servconf.c a/servconf.c --- a~/servconf.c 1970-01-01 00:00:00 +++ a/servconf.c 1970-01-01 00:00:00 @@ -2608,6 +2608,20 @@ process_server_config_line_depth(ServerO multistate_ptr = multistate_flag; goto parse_multistate; + case sPubKeyPlugin: + /* + * Can't use parse_filename, as we need to support plain + * names which dlopen will find on our lib path. + */ + charptr = &options->pubkey_plugin; + arg = argv_next(&ac, &av); + if (!arg || *arg == '\0') + fatal("%s line %d: missing file name.", + filename, linenum); + if (*activep && *charptr == NULL) + *charptr = xstrdup(arg); + break; + case sDeprecated: case sIgnore: case sUnsupported: @@ -4365,6 +4379,7 @@ dump_config(ServerOptions *o) dump_cfg_string(sHostKeyAgent, o->host_key_agent); dump_cfg_string(sKexAlgorithms, o->kex_algorithms); dump_cfg_string(sCASignatureAlgorithms, o->ca_sign_algorithms); + dump_cfg_string(sPubKeyPlugin, o->pubkey_plugin); dump_cfg_string(sHostbasedAcceptedAlgorithms, o->hostbased_accepted_algos); dump_cfg_string(sHostKeyAlgorithms, o->hostkeyalgorithms); dump_cfg_string(sPubkeyAcceptedAlgorithms, o->pubkey_accepted_algos); diff -wpruN --no-dereference '--exclude=*.orig' a~/servconf.h a/servconf.h --- a~/servconf.h 1970-01-01 00:00:00 +++ a/servconf.h 1970-01-01 00:00:00 @@ -235,7 +235,8 @@ SSHCONF_STRARRAY(channel_timeouts, num_c SSHCONF_INT(unused_connection_timeout, UnusedConnectionTimeout, SSHCFG_ALL, NULL, 0, SSHCFG_COPY_MATCH) \ SSHCONF_STRING(sshd_session_path, SshdSessionPath, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \ SSHCONF_STRING(sshd_auth_path, SshdAuthPath, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \ -SSHCONF_INTFLAG(refuse_connection, RefuseConnection, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) +SSHCONF_INTFLAG(refuse_connection, RefuseConnection, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \ +SSHCONF_STRING(pubkey_plugin, PubKeyPlugin, SSHCFG_ALL, SSHCFG_COPY_MATCH) #define SSHD_CONFIG_ENTRIES_LEGACY \ SSHCONF_DEPRECATE(ServerKeyBits, SSHCFG_GLOBAL, SSHCONF_DEPRECATED) \