diff --git a/.gitignore b/.gitignore index ea58cd8..35565f6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *.bb *.bbg *.prof +.*.swp /autom4te.cache /config.log /config.status @@ -20,3 +21,4 @@ config.h config.h.in configure default_options_guard.h +tags diff --git a/Makefile.in b/Makefile.in index be2d39e..e363fd9 100644 --- a/Makefile.in +++ b/Makefile.in @@ -80,6 +80,15 @@ else scpobjs=$(SCPOBJS) endif +ifeq (@DROPBEAR_EPKA@, 1) + # rdynamic makes all the global symbols of dropbear available to all the loaded shared libraries + # this allow a plugin to reuse existing crypto/utilities like base64_decode/base64_encode without + # the need to rewrite them. + EPKA_LIBS=-ldl -rdynamic +else + EPKA_LIBS= +endif + VPATH=@srcdir@ srcdir=@srcdir@ @@ -189,7 +198,7 @@ dropbearkey: $(dropbearkeyobjs) dropbearconvert: $(dropbearconvertobjs) dropbear: $(HEADERS) $(LIBTOM_DEPS) Makefile - $(CC) $(LDFLAGS) -o $@$(EXEEXT) $($@objs) $(LIBTOM_LIBS) $(LIBS) @CRYPTLIB@ + $(CC) $(LDFLAGS) -o $@$(EXEEXT) $($@objs) $(LIBTOM_LIBS) $(LIBS) @CRYPTLIB@ $(EPKA_LIBS) dbclient: $(HEADERS) $(LIBTOM_DEPS) Makefile $(CC) $(LDFLAGS) -o $@$(EXEEXT) $($@objs) $(LIBTOM_LIBS) $(LIBS) diff --git a/common-session.c b/common-session.c index aa31e49..a6449ca 100644 --- a/common-session.c +++ b/common-session.c @@ -147,6 +147,10 @@ void common_session_init(int sock_in, int sock_out) { ses.allowprivport = 0; +#if DROPBEAR_EPKA + ses.epka_session = NULL; +#endif + TRACE(("leave session_init")) } diff --git a/configure.ac b/configure.ac index 7199d7c..bbbfd02 100644 --- a/configure.ac +++ b/configure.ac @@ -323,6 +323,21 @@ AC_ARG_ENABLE(shadow, ] ) +AC_ARG_ENABLE(epka, + [ --enable-epka Enable support for External Public Key Authentication plug-in], + [ + AC_DEFINE(DROPBEAR_EPKA, 1, External Public Key Authentication) + AC_MSG_NOTICE(Enabling support for External Public Key Authentication) + DROPBEAR_EPKA=1 + ], + [ + AC_DEFINE(DROPBEAR_EPKA, 0, External Public Key Authentication) + DROPBEAR_EPKA=0 + ] + +) +AC_SUBST(DROPBEAR_EPKA) + AC_ARG_ENABLE(fuzz, [ --enable-fuzz Build fuzzing. Not recommended for deployment.], [ diff --git a/includes.h b/includes.h index 246882b..2fa26c4 100644 --- a/includes.h +++ b/includes.h @@ -164,6 +164,10 @@ typedef u_int32_t uint32_t; #include #endif +#if DROPBEAR_EPKA +#include +#endif + #include "fake-rfc2553.h" #include "fuzz.h" diff --git a/pubkeyapi.h b/pubkeyapi.h new file mode 100644 index 0000000..9ca9551 --- /dev/null +++ b/pubkeyapi.h @@ -0,0 +1,151 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ +#ifndef DROPBEAR_PUBKEY_H +#define DROPBEAR_PUBKEY_H + + +/* External Public Key API (EPKA) Plug-in Interface + * + * See: + * https://github.com/fabriziobertocci/dropbear-epka + * for additional information and examples about this API + * + */ + +struct EPKAInstance; +struct EPKASession; + +/* API VERSION INFORMATION - + * Dropbear will: + * - Reject any plugin with a major version mismatch + * - Load and print a warning if the plugin's minor version is HIGHER than + * dropbear's minor version (assumes properties are added at the end of + * EPKAInstance or EPKASession). This is a case of plugin newer than dropbear. + * - Reject if the plugin minor version is SMALLER than dropbear one (case + * of plugin older than dropbear). + * - Load (with no warnings) if version match. + */ +#define DROPBEAR_EPKA_VERSION_MAJOR 1 +#define DROPBEAR_EPKA_VERSION_MINOR 0 + + +/* Creates an instance of the plugin. + * + * This is the main entry point of the plug-in and should be IMMUTABLE across + * different API versions. Dropbear will check the version number + * returned in the api_version to match the version it understands and reject + * any plugin for which API major version does not match. + * + * If the version MINOR is different, dropbear will allow the plugin to run + * only if: plugin_MINOR > dropbear_MINOR + * + * If plugin_MINOR < dropbeart_MINOR or if the MAJOR version is different + * dropbear will reject the plugin and terminate the execution. + * + * addrstring is the IP address of the client. + * + * Returns NULL in case of failure, otherwise a void * of the instance that need + * to be passed to all the subsequent call to the plugin + */ +typedef struct EPKAInstance *(* PubkeyExtPlugin_newFn)(int verbose, + const char *options, + const char *addrstring); +#define DROPBEAR_PUBKEY_PLUGIN_FNNAME_NEW "plugin_new" + + +/* Validate a client through public key authentication + * + * If session has not been already created, creates it and store it + * in *sessionInOut. + * If session is a non-NULL, it will reuse it. + * + * Returns DROPBEAR_SUCCESS (0) if success or DROPBEAR_FAILURE (-1) if + * authentication fails + */ +typedef int (* PubkeyExtPlugin_checkPubKeyFn)(struct EPKAInstance *pluginInstance, + struct EPKASession **sessionInOut, + const char* algo, + unsigned int algolen, + const unsigned char* keyblob, + unsigned int keybloblen, + const char *username); + +/* Notify the plugin that auth completed (after signature verification) + */ +typedef void (* PubkeyExtPlugin_authSuccessFn)(struct EPKASession *session); + +/* Deletes a session + * TODO: Add a reason why the session is terminated. See svr_dropbear_exit (in svr-session.c) + */ +typedef void (* PubkeyExtPlugin_sessionDeleteFn)(struct EPKASession *session); + +/* Deletes the plugin instance */ +typedef void (* PubkeyExtPlugin_deleteFn)(struct EPKAInstance *pluginInstance); + + +/* The EPKAInstance object - A simple container of the pointer to the functions used + * by Dropbear. + * + * A plug-in can extend it to add its own properties + * + * The instance is created from the call to the plugin_new() function of the + * shared library. + * The delete_plugin function should delete the object. + */ +struct EPKAInstance { + int api_version[2]; /* 0=Major, 1=Minor */ + + PubkeyExtPlugin_checkPubKeyFn checkpubkey; /* mandatory */ + PubkeyExtPlugin_authSuccessFn auth_success; /* optional */ + PubkeyExtPlugin_sessionDeleteFn delete_session; /* mandatory */ + PubkeyExtPlugin_deleteFn delete_plugin; /* mandatory */ +}; + +/***************************************************************************** + * SESSION + ****************************************************************************/ +/* Returns the options from the session. + * The returned buffer will be destroyed when the session is deleted. + * Option buffer string NULL-terminated + */ +typedef char * (* PubkeyExtPlugin_getOptionsFn)(struct EPKASession *session); + + +/* An SSH Session. Created during pre-auth and reused during the authentication. + * The plug-in should delete this object (or any object extending it) from + * the delete_session() function. + * + * Extend it to cache user and authentication information that can be + * reused between pre-auth and auth (and to store whatever session-specific + * variable you need to keep). + * + * Store any optional auth options in the auth_options property of the session. + */ +struct EPKASession { + struct EPKAInstance * plugin_instance; + + PubkeyExtPlugin_getOptionsFn get_options; +}; + +#endif diff --git a/runopts.h b/runopts.h index 31eae1f..f173678 100644 --- a/runopts.h +++ b/runopts.h @@ -125,6 +125,11 @@ typedef struct svr_runopts { char * forced_command; +#if DROPBEAR_EPKA + char *pubkey_plugin; + char *pubkey_plugin_options; +#endif + } svr_runopts; extern svr_runopts svr_opts; diff --git a/session.h b/session.h index 0f77055..efade6e 100644 --- a/session.h +++ b/session.h @@ -38,6 +38,9 @@ #include "chansession.h" #include "dbutil.h" #include "netio.h" +#if DROPBEAR_EPKA +#include "pubkeyapi.h" +#endif void common_session_init(int sock_in, int sock_out); void session_loop(void(*loophandler)(void)) ATTRIB_NORETURN; @@ -216,6 +219,10 @@ struct sshsession { volatile int exitflag; /* set once the ses structure (and cli_ses/svr_ses) have been populated to their initial state */ int init_done; + +#if DROPBEAR_EPKA + struct EPKASession * epka_session; +#endif }; struct serversession { @@ -241,6 +248,14 @@ struct serversession { pid_t server_pid; #endif +#if DROPBEAR_EPKA + /* The shared library handle */ + void *epka_plugin_handle; + + /* The instance created by the plugin_new function */ + struct EPKAInstance *epka_instance; +#endif + }; typedef enum { diff --git a/svr-authpubkey.c b/svr-authpubkey.c index 4f27986..9d70bfb 100644 --- a/svr-authpubkey.c +++ b/svr-authpubkey.c @@ -91,6 +91,7 @@ void svr_auth_pubkey(int valid_user) { sign_key * key = NULL; char* fp = NULL; enum signkey_type type = -1; + int auth_failure = 1; TRACE(("enter pubkeyauth")) @@ -110,9 +111,45 @@ void svr_auth_pubkey(int valid_user) { send_msg_userauth_failure(0, 0); goto out; } +#if DROPBEAR_EPKA + if (svr_ses.epka_instance != NULL) { + char *options_buf; + if (svr_ses.epka_instance->checkpubkey( + svr_ses.epka_instance, + &ses.epka_session, + algo, + algolen, + keyblob, + keybloblen, + ses.authstate.username) == DROPBEAR_SUCCESS) { + /* Success */ + auth_failure = 0; + /* Options provided? */ + options_buf = ses.epka_session->get_options(ses.epka_session); + if (options_buf) { + struct buf temp_buf = { + .data = (unsigned char *)options_buf, + .len = strlen(options_buf), + .pos = 0, + .size = 0 + }; + int ret = svr_add_pubkey_options(&temp_buf, 0, "N/A"); + if (ret == DROPBEAR_FAILURE) { + /* Fail immediately as the plugin provided wrong options */ + send_msg_userauth_failure(0, 0); + goto out; + } + } + } + } +#endif /* check if the key is valid */ - if (checkpubkey(algo, algolen, keyblob, keybloblen) == DROPBEAR_FAILURE) { + if (auth_failure) { + auth_failure = checkpubkey(algo, algolen, keyblob, keybloblen) == DROPBEAR_FAILURE; + } + + if (auth_failure) { send_msg_userauth_failure(0, 0); goto out; } @@ -156,6 +193,13 @@ void svr_auth_pubkey(int valid_user) { "Pubkey auth succeeded for '%s' with key %s from %s", ses.authstate.pw_name, fp, svr_ses.addrstring); send_msg_userauth_success(); +#if DROPBEAR_EPKA + if ((ses.epka_session != NULL) && (svr_ses.epka_instance->auth_success != NULL)) { + /* Was authenticated through the external plugin. tell plugin that signature verification was ok */ + svr_ses.epka_instance->auth_success(ses.epka_session); + } +#endif + } else { dropbear_log(LOG_WARNING, "Pubkey auth bad signature for '%s' with key %s from %s", diff --git a/svr-runopts.c b/svr-runopts.c index d6c78df..19ce14c 100644 --- a/svr-runopts.c +++ b/svr-runopts.c @@ -46,16 +46,16 @@ static void printhelp(const char * progname) { "-b bannerfile Display the contents of bannerfile" " before user login\n" " (default: none)\n" - "-r keyfile Specify hostkeys (repeatable)\n" + "-r keyfile Specify hostkeys (repeatable)\n" " defaults: \n" #if DROPBEAR_DSS - " dss %s\n" + " - dss %s\n" #endif #if DROPBEAR_RSA - " rsa %s\n" + " - rsa %s\n" #endif #if DROPBEAR_ECDSA - " ecdsa %s\n" + " - ecdsa %s\n" #endif #if DROPBEAR_DELAY_HOSTKEY "-R Create hostkeys as required\n" @@ -99,6 +99,10 @@ static void printhelp(const char * progname) { "-W (default %d, larger may be faster, max 1MB)\n" "-K (0 is never, default %d, in seconds)\n" "-I (0 is never, default %d, in seconds)\n" +#if DROPBEAR_EPKA + "-A [,]\n" + " Enable external public key auth through \n" +#endif "-V Version\n" #if DEBUG_TRACE "-v verbose (compiled with DEBUG_TRACE)\n" @@ -129,6 +133,9 @@ void svr_getopts(int argc, char ** argv) { char* maxauthtries_arg = NULL; char* keyfile = NULL; char c; +#if DROPBEAR_EPKA + char* pubkey_plugin = NULL; +#endif /* see printhelp() for options */ @@ -156,6 +163,10 @@ void svr_getopts(int argc, char ** argv) { #if DROPBEAR_SVR_REMOTETCPFWD svr_opts.noremotetcp = 0; #endif +#if DROPBEAR_EPKA + svr_opts.pubkey_plugin = NULL; + svr_opts.pubkey_plugin_options = NULL; +#endif #ifndef DISABLE_ZLIB opts.compress_mode = DROPBEAR_COMPRESS_DELAYED; @@ -274,6 +285,11 @@ void svr_getopts(int argc, char ** argv) { case 'u': /* backwards compatibility with old urandom option */ break; +#if DROPBEAR_EPKA + case 'A': + next = &pubkey_plugin; + break; +#endif #if DEBUG_TRACE case 'v': debug_trace = 1; @@ -394,6 +410,17 @@ void svr_getopts(int argc, char ** argv) { if (svr_opts.forced_command) { dropbear_log(LOG_INFO, "Forced command set to '%s'", svr_opts.forced_command); } +#if DROPBEAR_EPKA + if (pubkey_plugin) { + char *args = strchr(pubkey_plugin, ','); + if (args) { + *args='\0'; + ++args; + } + svr_opts.pubkey_plugin = pubkey_plugin; + svr_opts.pubkey_plugin_options = args; + } +#endif } static void addportandaddress(const char* spec) { diff --git a/svr-session.c b/svr-session.c index a816398..3ea7589 100644 --- a/svr-session.c +++ b/svr-session.c @@ -88,6 +88,18 @@ svr_session_cleanup(void) { m_free(svr_ses.remotehost); m_free(svr_ses.childpids); svr_ses.childpidsize = 0; + +#if DROPBEAR_EPKA + if (svr_ses.epka_plugin_handle != NULL) { + if (svr_ses.epka_instance) { + svr_ses.epka_instance->delete_plugin(svr_ses.epka_instance); + svr_ses.epka_instance = NULL; + } + + dlclose(svr_ses.epka_plugin_handle); + svr_ses.epka_plugin_handle = NULL; + } +#endif } void svr_session(int sock, int childpipe) { @@ -101,10 +113,6 @@ void svr_session(int sock, int childpipe) { #if DROPBEAR_VFORK svr_ses.server_pid = getpid(); #endif - svr_authinitialise(); - chaninitialise(svr_chantypes); - svr_chansessinitialise(); - svr_algos_initialise(); /* for logging the remote address */ get_socket_address(ses.sock_in, NULL, NULL, &host, &port, 0); @@ -114,6 +122,56 @@ void svr_session(int sock, int childpipe) { m_free(host); m_free(port); +#if DROPBEAR_EPKA + /* Initializes the EPKA Plugin */ + svr_ses.epka_plugin_handle = NULL; + svr_ses.epka_instance = NULL; + if (svr_opts.pubkey_plugin) { +#if DEBUG_TRACE + const int verbose = debug_trace; +#else + const int verbose = 0; +#endif + PubkeyExtPlugin_newFn pluginConstructor; + + /* RTLD_NOW: fails if not all the symbols are resolved now. Better fail now than at run-time */ + svr_ses.epka_plugin_handle = dlopen(svr_opts.pubkey_plugin, RTLD_NOW); + if (svr_ses.epka_plugin_handle == NULL) { + dropbear_exit("failed to load external pubkey plugin '%s': %s", svr_opts.pubkey_plugin, dlerror()); + } + pluginConstructor = (PubkeyExtPlugin_newFn)dlsym(svr_ses.epka_plugin_handle, DROPBEAR_PUBKEY_PLUGIN_FNNAME_NEW); + if (!pluginConstructor) { + dropbear_exit("plugin constructor method not found in external pubkey plugin"); + } + + /* Create an instance of the plugin */ + svr_ses.epka_instance = pluginConstructor(verbose, svr_opts.pubkey_plugin_options, svr_ses.addrstring); + if (svr_ses.epka_instance == NULL) { + dropbear_exit("external plugin initialization failed"); + } + /* Check if the plugin is compatible */ + if ( (svr_ses.epka_instance->api_version[0] != DROPBEAR_EPKA_VERSION_MAJOR) || + (svr_ses.epka_instance->api_version[1] < DROPBEAR_EPKA_VERSION_MINOR) ) { + dropbear_exit("plugin version check failed: " + "Dropbear=%d.%d, plugin=%d.%d", + DROPBEAR_EPKA_VERSION_MAJOR, DROPBEAR_EPKA_VERSION_MINOR, + svr_ses.epka_instance->api_version[0], svr_ses.epka_instance->api_version[1]); + } + if (svr_ses.epka_instance->api_version[1] > DROPBEAR_EPKA_VERSION_MINOR) { + dropbear_log(LOG_WARNING, "plugin API newer than dropbear API: " + "Dropbear=%d.%d, plugin=%d.%d", + DROPBEAR_EPKA_VERSION_MAJOR, DROPBEAR_EPKA_VERSION_MINOR, + svr_ses.epka_instance->api_version[0], svr_ses.epka_instance->api_version[1]); + } + dropbear_log(LOG_INFO, "successfully loaded and initialized pubkey plugin '%s'", svr_opts.pubkey_plugin); + } +#endif + + svr_authinitialise(); + chaninitialise(svr_chantypes); + svr_chansessinitialise(); + svr_algos_initialise(); + get_socket_address(ses.sock_in, NULL, NULL, &svr_ses.remotehost, NULL, 1); @@ -151,6 +209,13 @@ void svr_dropbear_exit(int exitcode, const char* format, va_list param) { char fullmsg[300]; int i; +#if DROPBEAR_EPKA + if ((ses.epka_session != NULL)) { + svr_ses.epka_instance->delete_session(ses.epka_session); + } + ses.epka_session = NULL; +#endif + /* Render the formatted exit message */ vsnprintf(exitmsg, sizeof(exitmsg), format, param); diff --git a/sysoptions.h b/sysoptions.h index efb27d8..58604f0 100644 --- a/sysoptions.h +++ b/sysoptions.h @@ -243,6 +243,9 @@ If you test it please contact the Dropbear author */ #error "At least one server authentication type must be enabled. DROPBEAR_SVR_PUBKEY_AUTH and DROPBEAR_SVR_PASSWORD_AUTH are recommended." #endif +#if (DROPBEAR_EPKA && !DROPBEAR_SVR_PUBKEY_AUTH) + #error "You must define DROPBEAR_SVR_PUBKEY_AUTH in order to use External Public Key Authentication (EPKA)" +#endif #if !(DROPBEAR_AES128 || DROPBEAR_3DES || DROPBEAR_AES256 || DROPBEAR_BLOWFISH \ || DROPBEAR_TWOFISH256 || DROPBEAR_TWOFISH128)