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