1 From 3940b5d3e954d472b52da4aed4f8e6f3725411d2 Mon Sep 17 00:00:00 2001
   2 From: Alex Wilson <alex.wilson@joyent.com>
   3 Date: Mon, 3 Aug 2015 16:27:44 -0700
   4 Subject: [PATCH 21/36] PubKeyPlugin support
   5 
   6 This adds the PubKeyPlugin directive and associated code from
   7 SunSSH, allowing an in-process shared library to be called
   8 into to check public keys for authentication.
   9 ---
  10  auth2-pubkey.c | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  11  servconf.c     |  15 +++++++
  12  servconf.h     |   1 +
  13  3 files changed, 155 insertions(+)
  14 
  15 diff --git a/auth2-pubkey.c b/auth2-pubkey.c
  16 index 5aa319c..77b00f7 100644
  17 --- a/auth2-pubkey.c
  18 +++ b/auth2-pubkey.c
  19 @@ -22,6 +22,11 @@
  20   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  21   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  22   */
  23 +/*
  24 + * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  25 + * Copyright 2015 Joyent, Inc.
  26 + * Use is subject to license terms.
  27 + */
  28  
  29  #include "includes.h"
  30  
  31 @@ -42,11 +47,13 @@
  32  #include <time.h>
  33  #include <unistd.h>
  34  #include <limits.h>
  35 +#include <dlfcn.h>
  36  
  37  #include "xmalloc.h"
  38  #include "ssh.h"
  39  #include "ssh2.h"
  40  #include "packet.h"
  41 +#include "digest.h"
  42  #include "buffer.h"
  43  #include "log.h"
  44  #include "misc.h"
  45 @@ -74,6 +81,15 @@ extern ServerOptions options;
  46  extern u_char *session_id2;
  47  extern u_int session_id2_len;
  48  
  49 +static const char *RSA_SYM_NAME = "sshd_user_rsa_key_allowed";
  50 +static const char *ECDSA_SYM_NAME = "sshd_user_ecdsa_key_allowed";
  51 +typedef int (*RSA_SYM)(struct passwd *, RSA *, const char *);
  52 +typedef int (*ECDSA_SYM)(struct passwd *, EC_KEY *, const char *);
  53 +
  54 +static const char *UNIV_SYM_NAME = "sshd_user_key_allowed";
  55 +typedef int (*UNIV_SYM)(struct passwd *, const char *,
  56 +    const u_char *, size_t);
  57 +
  58  static int
  59  userauth_pubkey(Authctxt *authctxt)
  60  {
  61 @@ -1030,6 +1046,125 @@ user_key_command_allowed2(struct passwd *user_pw, Key *key)
  62         return found_key;
  63  }
  64  
  65 +/**
  66 + * Checks whether or not access is allowed based on a plugin specified
  67 + * in sshd_config (PubKeyPlugin).
  68 + *
  69 + * Note that this expects a symbol in the loaded library that takes
  70 + * the current user (pwd entry), the current RSA key and it's fingerprint.
  71 + * The symbol is expected to return 1 on success and 0 on failure.
  72 + *
  73 + * While we could optimize this code to dlopen once in the process' lifetime,
  74 + * sshd is already a slow beast, so this is really not a concern.
  75 + * The overhead is basically a rounding error compared to everything else, and
  76 + * it keeps this code minimally invasive.
  77 + */
  78 +static int
  79 +user_key_allowed_from_plugin(struct passwd *pw, Key *key)
  80 +{
  81 +       RSA_SYM rsa_sym = NULL;
  82 +       ECDSA_SYM ecdsa_sym = NULL;
  83 +       UNIV_SYM univ_sym = NULL;
  84 +       char *fp = NULL;
  85 +       char *argfp = NULL;
  86 +       void *handle = NULL;
  87 +       int success = 0;
  88 +
  89 +       if (options.pubkey_plugin == NULL || pw == NULL || key == NULL ||
  90 +           (key->type != KEY_RSA && key->type != KEY_RSA1 &&
  91 +           key->type != KEY_DSA && key->type != KEY_ECDSA))
  92 +               return success;
  93 +
  94 +       handle = dlopen(options.pubkey_plugin, RTLD_NOW);
  95 +       if (handle == NULL) {
  96 +               debug("Unable to open library %s: %s", options.pubkey_plugin,
  97 +               dlerror());
  98 +               goto out;
  99 +       }
 100 +
 101 +       /*
 102 +        * If we have the new-style universal symbol for checking keys, use
 103 +        * that instead of the old-style per-key-type symbols.
 104 +        */
 105 +       univ_sym = (UNIV_SYM)dlsym(handle, UNIV_SYM_NAME);
 106 +       if (univ_sym != NULL) {
 107 +               u_char *blob;
 108 +               const char *type = sshkey_type(key);
 109 +               size_t len = 0;
 110 +               if (sshkey_to_blob(key, &blob, &len) != 0) {
 111 +                       debug("failed to convert key to rfc4253 format");
 112 +                       goto out;
 113 +               }
 114 +               debug("Invoking %s from %s", UNIV_SYM_NAME,
 115 +                   options.pubkey_plugin);
 116 +               success = (*univ_sym)(pw, type, blob, len);
 117 +               debug("pubkeyplugin returned: %d", success);
 118 +               goto out;
 119 +       }
 120 +
 121 +       /* Otherwise, continue with the old-style fingerprint symbols. */
 122 +       fp = sshkey_fingerprint(key, SSH_DIGEST_MD5, SSH_FP_HEX);
 123 +       if (fp == NULL) {
 124 +               debug("failed to generate fingerprint");
 125 +               goto out;
 126 +       }
 127 +       if (strncmp(fp, "MD5:", 4) != 0) {
 128 +               debug("fingerprint not in MD5:hex format");
 129 +               goto out;
 130 +       }
 131 +       /* give the plugin the string without leading MD5: */
 132 +       argfp = fp + 4;
 133 +
 134 +       switch (key->type) {
 135 +       case KEY_RSA1:
 136 +       case KEY_RSA:
 137 +               rsa_sym = (RSA_SYM)dlsym(handle, RSA_SYM_NAME);
 138 +               if (rsa_sym == NULL) {
 139 +                       debug("Unable to resolve symbol %s: %s", RSA_SYM_NAME,
 140 +                       dlerror());
 141 +                       goto out;
 142 +               }
 143 +               debug2("Invoking %s from %s", RSA_SYM_NAME,
 144 +                   options.pubkey_plugin);
 145 +               success = (*rsa_sym)(pw, key->rsa, argfp);
 146 +               break;
 147 +       case KEY_ECDSA:
 148 +               ecdsa_sym = (ECDSA_SYM)dlsym(handle, ECDSA_SYM_NAME);
 149 +               if (ecdsa_sym == NULL) {
 150 +                       debug("Unable to resolve symbol %s: %s", ECDSA_SYM_NAME,
 151 +                       dlerror());
 152 +                       goto out;
 153 +               }
 154 +               debug2("Invoking %s from %s", ECDSA_SYM_NAME,
 155 +                   options.pubkey_plugin);
 156 +               success = (*ecdsa_sym)(pw, key->ecdsa, argfp);
 157 +               break;
 158 +       default:
 159 +               debug2("user_key_plugins only support RSA and ECDSA keys");
 160 +       }
 161 +
 162 +       debug("pubkeyplugin returned: %d", success);
 163 +
 164 +out:
 165 +       if (handle != NULL) {
 166 +               dlclose(handle);
 167 +               ecdsa_sym = NULL;
 168 +               rsa_sym = NULL;
 169 +               univ_sym = NULL;
 170 +               handle = NULL;
 171 +       }
 172 +
 173 +       if (success)
 174 +               verbose("Found matching %s key: %s", key_type(key), fp);
 175 +
 176 +       if (fp != NULL) {
 177 +               free(fp);
 178 +               fp = NULL;
 179 +       }
 180 +
 181 +       return success;
 182 +}
 183 +
 184  /*
 185   * Check whether key authenticates and authorises the user.
 186   */
 187 @@ -1048,6 +1183,10 @@ user_key_allowed(struct passwd *pw, Key *key, int auth_attempt)
 188         if (success)
 189                 return success;
 190  
 191 +       success = user_key_allowed_from_plugin(pw, key);
 192 +       if (success > 0)
 193 +               return success;
 194 +
 195         success = user_key_command_allowed2(pw, key);
 196         if (success > 0)
 197                 return success;
 198 diff --git a/servconf.c b/servconf.c
 199 index 53146af..dca59fd 100644
 200 --- a/servconf.c
 201 +++ b/servconf.c
 202 @@ -182,6 +182,7 @@ initialize_server_options(ServerOptions *options)
 203          */
 204         options->pam_service_per_authmethod = 1;
 205  #endif
 206 +       options->pubkey_plugin = NULL;
 207  }
 208  
 209  /* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */
 210 @@ -465,6 +466,7 @@ typedef enum {
 211         sAuthenticationMethods, sHostKeyAgent, sPermitUserRC,
 212         sStreamLocalBindMask, sStreamLocalBindUnlink,
 213         sAllowStreamLocalForwarding, sFingerprintHash,
 214 +       sPubKeyPlugin,
 215         sDeprecated, sUnsupported
 216  } ServerOpCodes;
 217  
 218 @@ -640,6 +642,7 @@ static struct {
 219         { "streamlocalbindunlink", sStreamLocalBindUnlink, SSHCFG_ALL },
 220         { "allowstreamlocalforwarding", sAllowStreamLocalForwarding, SSHCFG_ALL },
 221         { "fingerprinthash", sFingerprintHash, SSHCFG_GLOBAL },
 222 +       { "pubkeyplugin", sPubKeyPlugin, SSHCFG_ALL },
 223         { NULL, sBadOption, 0 }
 224  };
 225  
 226 @@ -1966,6 +1969,18 @@ process_server_config_line(ServerOptions *options, char *line,
 227                 }
 228                 break;
 229  
 230 +       case sPubKeyPlugin:
 231 +               /*
 232 +                * Can't use parse_filename, as we need to support plain
 233 +                * names which dlopen will find on our lib path.
 234 +                */
 235 +               arg = strdelim(&cp);
 236 +               if (!arg || *arg == '\0')
 237 +                       fatal("%s line %d: missing file name.",
 238 +                           filename, linenum);
 239 +               options->pubkey_plugin = xstrdup(arg);
 240 +               break;
 241 +
 242         case sDeprecated:
 243                 logit("%s line %d: Deprecated option %s",
 244                     filename, linenum, arg);
 245 diff --git a/servconf.h b/servconf.h
 246 index 2175645..80e152f 100644
 247 --- a/servconf.h
 248 +++ b/servconf.h
 249 @@ -206,6 +206,7 @@ typedef struct {
 250  #endif
 251          
 252         int     fingerprint_hash;
 253 +       char   *pubkey_plugin;
 254  }       ServerOptions;
 255  
 256  /* Information about the incoming connection as used by Match */
 257 -- 
 258 2.5.4 (Apple Git-61)
 259