/src/dropbear/src/svr-authpubkey.c
Line | Count | Source |
1 | | /* |
2 | | * Dropbear - a SSH2 server |
3 | | * |
4 | | * Copyright (c) 2002,2003 Matt Johnston |
5 | | * All rights reserved. |
6 | | * |
7 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
8 | | * of this software and associated documentation files (the "Software"), to deal |
9 | | * in the Software without restriction, including without limitation the rights |
10 | | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
11 | | * copies of the Software, and to permit persons to whom the Software is |
12 | | * furnished to do so, subject to the following conditions: |
13 | | * |
14 | | * The above copyright notice and this permission notice shall be included in |
15 | | * all copies or substantial portions of the Software. |
16 | | * |
17 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
18 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
19 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
20 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
21 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
22 | | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
23 | | * SOFTWARE. */ |
24 | | /* |
25 | | * This file incorporates work covered by the following copyright and |
26 | | * permission notice: |
27 | | * |
28 | | * Copyright (c) 2000 Markus Friedl. All rights reserved. |
29 | | * |
30 | | * Redistribution and use in source and binary forms, with or without |
31 | | * modification, are permitted provided that the following conditions |
32 | | * are met: |
33 | | * 1. Redistributions of source code must retain the above copyright |
34 | | * notice, this list of conditions and the following disclaimer. |
35 | | * 2. Redistributions in binary form must reproduce the above copyright |
36 | | * notice, this list of conditions and the following disclaimer in the |
37 | | * documentation and/or other materials provided with the distribution. |
38 | | * |
39 | | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
40 | | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
41 | | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
42 | | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
43 | | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
44 | | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
45 | | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
46 | | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
47 | | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
48 | | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
49 | | * |
50 | | * This copyright and permission notice applies to the code parsing public keys |
51 | | * options string which can also be found in OpenSSH auth2-pubkey.c file |
52 | | * (user_key_allowed2). It has been adapted to work with buffers. |
53 | | * |
54 | | */ |
55 | | |
56 | | /* Process a pubkey auth request */ |
57 | | |
58 | | #include "includes.h" |
59 | | #include "session.h" |
60 | | #include "dbutil.h" |
61 | | #include "buffer.h" |
62 | | #include "signkey.h" |
63 | | #include "auth.h" |
64 | | #include "ssh.h" |
65 | | #include "packet.h" |
66 | | #include "algo.h" |
67 | | #include "runopts.h" |
68 | | |
69 | | #if DROPBEAR_SVR_PUBKEY_AUTH |
70 | | |
71 | 0 | #define MIN_AUTHKEYS_LINE 10 /* "ssh-rsa AB" - short but doesn't matter */ |
72 | 0 | #define MAX_AUTHKEYS_LINE 4200 /* max length of a line in authkeys */ |
73 | | |
74 | | static char * authorized_keys_filepath(void); |
75 | | static int checkpubkey(const char* keyalgo, unsigned int keyalgolen, |
76 | | const unsigned char* keyblob, unsigned int keybloblen, |
77 | | struct PubKeyOptions **ret_options); |
78 | | static int checkpubkeyperms(void); |
79 | | static void send_msg_userauth_pk_ok(const char* sigalgo, unsigned int sigalgolen, |
80 | | const unsigned char* keyblob, unsigned int keybloblen); |
81 | | static int checkfileperm(char * filename); |
82 | | |
83 | | /* process a pubkey auth request, sending success or failure message as |
84 | | * appropriate */ |
85 | 0 | void svr_auth_pubkey(int valid_user) { |
86 | |
|
87 | 0 | unsigned char testkey; /* whether we're just checking if a key is usable */ |
88 | 0 | char* sigalgo = NULL; |
89 | 0 | unsigned int sigalgolen; |
90 | 0 | const char* keyalgo; |
91 | 0 | unsigned int keyalgolen; |
92 | 0 | unsigned char* keyblob = NULL; |
93 | 0 | unsigned int keybloblen; |
94 | 0 | unsigned int sign_payload_length; |
95 | 0 | buffer * signbuf = NULL; |
96 | 0 | sign_key * key = NULL; |
97 | 0 | char* fp = NULL; |
98 | 0 | enum signature_type sigtype; |
99 | 0 | enum signkey_type keytype; |
100 | 0 | int auth_failure = 1; |
101 | 0 | struct PubKeyOptions *pubkey_options = NULL; |
102 | |
|
103 | 0 | TRACE(("enter pubkeyauth")) |
104 | | |
105 | | /* 0 indicates user just wants to check if key can be used, 1 is an |
106 | | * actual attempt*/ |
107 | 0 | testkey = (buf_getbool(ses.payload) == 0); |
108 | |
|
109 | 0 | sigalgo = buf_getstring(ses.payload, &sigalgolen); |
110 | 0 | keybloblen = buf_getint(ses.payload); |
111 | 0 | keyblob = buf_getptr(ses.payload, keybloblen); |
112 | |
|
113 | 0 | if (!valid_user) { |
114 | | /* Return failure once we have read the contents of the packet |
115 | | required to validate a public key. |
116 | | Avoids blind user enumeration though it isn't possible to prevent |
117 | | testing for user existence if the public key is known */ |
118 | 0 | send_msg_userauth_failure(0, 0); |
119 | 0 | goto out; |
120 | 0 | } |
121 | | |
122 | 0 | sigtype = signature_type_from_name(sigalgo, sigalgolen); |
123 | 0 | if (sigtype == DROPBEAR_SIGNATURE_NONE) { |
124 | 0 | send_msg_userauth_failure(0, 0); |
125 | 0 | goto out; |
126 | 0 | } |
127 | | |
128 | 0 | keytype = signkey_type_from_signature(sigtype); |
129 | 0 | keyalgo = signkey_name_from_type(keytype, &keyalgolen); |
130 | |
|
131 | | #if DROPBEAR_PLUGIN |
132 | | if (svr_ses.plugin_instance != NULL) { |
133 | | char *options_buf; |
134 | | if (svr_ses.plugin_instance->checkpubkey( |
135 | | svr_ses.plugin_instance, |
136 | | &ses.plugin_session, |
137 | | keyalgo, |
138 | | keyalgolen, |
139 | | keyblob, |
140 | | keybloblen, |
141 | | ses.authstate.username) == DROPBEAR_SUCCESS) { |
142 | | /* Success */ |
143 | | auth_failure = 0; |
144 | | |
145 | | /* Options provided? */ |
146 | | options_buf = ses.plugin_session->get_options(ses.plugin_session); |
147 | | if (options_buf) { |
148 | | struct buf temp_buf = { |
149 | | .data = (unsigned char *)options_buf, |
150 | | .len = strlen(options_buf), |
151 | | .pos = 0, |
152 | | .size = 0 |
153 | | }; |
154 | | pubkey_options = svr_parse_pubkey_options(&temp_buf, 0, "plugin"); |
155 | | if (pubkey_options == NULL) { |
156 | | /* Fail immediately as the plugin provided wrong options */ |
157 | | send_msg_userauth_failure(0, 0); |
158 | | goto out; |
159 | | } |
160 | | } |
161 | | } |
162 | | } |
163 | | #endif |
164 | | /* check if the key is valid */ |
165 | 0 | if (auth_failure) { |
166 | 0 | int status = checkpubkey(keyalgo, keyalgolen, keyblob, keybloblen, &pubkey_options); |
167 | 0 | auth_failure = (status != DROPBEAR_SUCCESS); |
168 | 0 | } |
169 | |
|
170 | 0 | if (auth_failure) { |
171 | | /* MAX_PUBKEY_QUERIES allows a greater limit of pubkey queries |
172 | | * than the standard maxauthtries. |
173 | | * Start counting failures (incrfail) only when it's reaching |
174 | | * the limit. |
175 | | */ |
176 | 0 | unsigned int free_query_limit = |
177 | 0 | MAX(0, MAX_PUBKEY_QUERIES - (int)svr_opts.maxauthtries); |
178 | 0 | int incrfail = ses.authstate.serv_pubkey_query_count >= free_query_limit; |
179 | 0 | send_msg_userauth_failure(0, incrfail); |
180 | 0 | ses.authstate.serv_pubkey_query_count++; |
181 | 0 | goto out; |
182 | 0 | } |
183 | | |
184 | | /* let them know that the key is ok to use */ |
185 | 0 | if (testkey) { |
186 | 0 | send_msg_userauth_pk_ok(sigalgo, sigalgolen, keyblob, keybloblen); |
187 | 0 | goto out; |
188 | 0 | } |
189 | | |
190 | | /* now we can actually verify the signature */ |
191 | | |
192 | | /* get the key */ |
193 | 0 | key = new_sign_key(); |
194 | 0 | if (buf_get_pub_key(ses.payload, key, &keytype) == DROPBEAR_FAILURE) { |
195 | 0 | send_msg_userauth_failure(0, 1); |
196 | 0 | goto out; |
197 | 0 | } |
198 | | |
199 | 0 | #if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519 |
200 | 0 | key->sk_flags_mask = SSH_SK_USER_PRESENCE_REQD; |
201 | 0 | #if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT |
202 | 0 | if (pubkey_options->no_touch_required_flag) { |
203 | 0 | key->sk_flags_mask &= ~SSH_SK_USER_PRESENCE_REQD; |
204 | 0 | } |
205 | 0 | if (pubkey_options->verify_required_flag) { |
206 | 0 | key->sk_flags_mask |= SSH_SK_USER_VERIFICATION_REQD; |
207 | 0 | } |
208 | 0 | #endif /* DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT */ |
209 | 0 | #endif |
210 | | |
211 | | /* create the data which has been signed - this a string containing |
212 | | * session_id, concatenated with the payload packet up to the signature */ |
213 | 0 | assert(ses.payload_beginning <= ses.payload->pos); |
214 | 0 | sign_payload_length = ses.payload->pos - ses.payload_beginning; |
215 | 0 | signbuf = buf_new(ses.payload->pos + 4 + ses.session_id->len); |
216 | 0 | buf_putbufstring(signbuf, ses.session_id); |
217 | | |
218 | | /* The entire contents of the payload prior. */ |
219 | 0 | buf_setpos(ses.payload, ses.payload_beginning); |
220 | 0 | buf_putbytes(signbuf, |
221 | 0 | buf_getptr(ses.payload, sign_payload_length), |
222 | 0 | sign_payload_length); |
223 | 0 | buf_incrpos(ses.payload, sign_payload_length); |
224 | |
|
225 | 0 | buf_setpos(signbuf, 0); |
226 | | |
227 | | /* ... and finally verify the signature */ |
228 | 0 | fp = sign_key_fingerprint(keyblob, keybloblen); |
229 | 0 | if (buf_verify(ses.payload, key, sigtype, signbuf) == DROPBEAR_SUCCESS) { |
230 | 0 | if (ses.authstate.pubkey_options == NULL) { |
231 | 0 | ses.authstate.pubkey_options = pubkey_options; |
232 | 0 | pubkey_options = NULL; |
233 | 0 | } |
234 | 0 | if (svr_opts.multiauthmethod && (ses.authstate.authtypes & ~AUTH_TYPE_PUBKEY)) { |
235 | | /* successful pubkey authentication, but extra auth required */ |
236 | 0 | dropbear_log(LOG_NOTICE, |
237 | 0 | "Pubkey auth succeeded for '%s' with %s key %s from %s, extra auth required", |
238 | 0 | ses.authstate.pw_name, |
239 | 0 | signkey_name_from_type(keytype, NULL), fp, |
240 | 0 | svr_ses.addrstring); |
241 | 0 | ses.authstate.authtypes &= ~AUTH_TYPE_PUBKEY; /* pubkey auth ok, delete the method flag */ |
242 | 0 | send_msg_userauth_failure(1, 0); /* Send partial success */ |
243 | 0 | } else { |
244 | | /* successful authentication */ |
245 | 0 | dropbear_log(LOG_NOTICE, |
246 | 0 | "Pubkey auth succeeded for '%s' with %s key %s from %s", |
247 | 0 | ses.authstate.pw_name, |
248 | 0 | signkey_name_from_type(keytype, NULL), fp, |
249 | 0 | svr_ses.addrstring); |
250 | 0 | send_msg_userauth_success(); |
251 | 0 | } |
252 | | #if DROPBEAR_PLUGIN |
253 | | if ((ses.plugin_session != NULL) && (svr_ses.plugin_instance->auth_success != NULL)) { |
254 | | /* Was authenticated through the external plugin. tell plugin that signature verification was ok */ |
255 | | svr_ses.plugin_instance->auth_success(ses.plugin_session); |
256 | | } |
257 | | #endif |
258 | 0 | } else { |
259 | 0 | dropbear_log(LOG_WARNING, |
260 | 0 | "Pubkey auth bad signature for '%s' with key %s from %s", |
261 | 0 | ses.authstate.pw_name, fp, svr_ses.addrstring); |
262 | 0 | send_msg_userauth_failure(0, 1); |
263 | 0 | } |
264 | 0 | m_free(fp); |
265 | |
|
266 | 0 | out: |
267 | | /* cleanup stuff */ |
268 | 0 | if (signbuf) { |
269 | 0 | buf_free(signbuf); |
270 | 0 | } |
271 | 0 | if (sigalgo) { |
272 | 0 | m_free(sigalgo); |
273 | 0 | } |
274 | 0 | if (key) { |
275 | 0 | sign_key_free(key); |
276 | 0 | key = NULL; |
277 | 0 | } |
278 | 0 | if (pubkey_options) { |
279 | 0 | svr_pubkey_options_cleanup(pubkey_options); |
280 | 0 | pubkey_options = NULL; |
281 | 0 | } |
282 | 0 | TRACE(("leave pubkeyauth")) |
283 | 0 | } |
284 | | |
285 | | /* Reply that the key is valid for auth, this is sent when the user sends |
286 | | * a straight copy of their pubkey to test, to avoid having to perform |
287 | | * expensive signing operations with a worthless key */ |
288 | | static void send_msg_userauth_pk_ok(const char* sigalgo, unsigned int sigalgolen, |
289 | 0 | const unsigned char* keyblob, unsigned int keybloblen) { |
290 | |
|
291 | 0 | TRACE(("enter send_msg_userauth_pk_ok")) |
292 | 0 | CHECKCLEARTOWRITE(); |
293 | |
|
294 | 0 | buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_PK_OK); |
295 | 0 | buf_putstring(ses.writepayload, sigalgo, sigalgolen); |
296 | 0 | buf_putstring(ses.writepayload, (const char*)keyblob, keybloblen); |
297 | |
|
298 | 0 | encrypt_packet(); |
299 | 0 | TRACE(("leave send_msg_userauth_pk_ok")) |
300 | |
|
301 | 0 | } |
302 | | |
303 | | /* Key options are optionally returned in ret_options. |
304 | | Should be passed with *ret_options = NULL, will only be populated |
305 | | on success return. */ |
306 | | static int checkpubkey_line(buffer* line, int line_num, const char* filename, |
307 | | const char* algo, unsigned int algolen, |
308 | | const unsigned char* keyblob, unsigned int keybloblen, |
309 | 0 | struct PubKeyOptions ** ret_options) { |
310 | 0 | buffer *options_buf = NULL; |
311 | 0 | char *info_str = NULL; |
312 | 0 | unsigned int pos, len, infopos, infolen; |
313 | 0 | int ret = DROPBEAR_FAILURE; |
314 | |
|
315 | 0 | if (ret_options) { |
316 | 0 | *ret_options = NULL; |
317 | 0 | } |
318 | |
|
319 | 0 | if (line->len < MIN_AUTHKEYS_LINE || line->len > MAX_AUTHKEYS_LINE) { |
320 | 0 | TRACE(("checkpubkey_line: bad line length %d", line->len)) |
321 | 0 | goto out; |
322 | 0 | } |
323 | | |
324 | 0 | if (memchr(line->data, 0x0, line->len) != NULL) { |
325 | 0 | TRACE(("checkpubkey_line: bad line has null char")) |
326 | 0 | goto out; |
327 | 0 | } |
328 | | |
329 | | /* compare the algorithm. +3 so we have enough bytes to read a space and some base64 characters too. */ |
330 | 0 | if (line->pos + algolen+3 > line->len) { |
331 | 0 | goto out; |
332 | 0 | } |
333 | | /* check the key type */ |
334 | 0 | if (strncmp((const char *) buf_getptr(line, algolen), algo, algolen) != 0) { |
335 | 0 | int is_comment = 0; |
336 | 0 | unsigned char *options_start = NULL; |
337 | 0 | int options_len = 0; |
338 | 0 | int escape, quoted; |
339 | | |
340 | | /* skip over any comments or leading whitespace */ |
341 | 0 | while (line->pos < line->len) { |
342 | 0 | const char c = buf_getbyte(line); |
343 | 0 | if (c == ' ' || c == '\t') { |
344 | 0 | continue; |
345 | 0 | } else if (c == '#') { |
346 | 0 | is_comment = 1; |
347 | 0 | break; |
348 | 0 | } |
349 | 0 | buf_decrpos(line, 1); |
350 | 0 | break; |
351 | 0 | } |
352 | 0 | if (is_comment) { |
353 | | /* next line */ |
354 | 0 | goto out; |
355 | 0 | } |
356 | | |
357 | | /* remember start of options */ |
358 | 0 | options_start = buf_getptr(line, 1); |
359 | 0 | quoted = 0; |
360 | 0 | escape = 0; |
361 | 0 | options_len = 0; |
362 | | |
363 | | /* figure out where the options are */ |
364 | 0 | while (line->pos < line->len) { |
365 | 0 | const char c = buf_getbyte(line); |
366 | 0 | if (!quoted && (c == ' ' || c == '\t')) { |
367 | 0 | break; |
368 | 0 | } |
369 | 0 | escape = (!escape && c == '\\'); |
370 | 0 | if (!escape && c == '"') { |
371 | 0 | quoted = !quoted; |
372 | 0 | } |
373 | 0 | options_len++; |
374 | 0 | } |
375 | 0 | options_buf = buf_new(options_len); |
376 | 0 | buf_putbytes(options_buf, options_start, options_len); |
377 | | |
378 | | /* compare the algorithm. +3 so we have enough bytes to read a space and some base64 characters too. */ |
379 | 0 | if (line->pos + algolen+3 > line->len) { |
380 | 0 | goto out; |
381 | 0 | } |
382 | 0 | if (strncmp((const char *) buf_getptr(line, algolen), algo, algolen) != 0) { |
383 | 0 | goto out; |
384 | 0 | } |
385 | 0 | } |
386 | 0 | buf_incrpos(line, algolen); |
387 | | |
388 | | /* check for space (' ') character */ |
389 | 0 | if (buf_getbyte(line) != ' ') { |
390 | 0 | TRACE(("checkpubkey_line: space character expected, isn't there")) |
391 | 0 | goto out; |
392 | 0 | } |
393 | | |
394 | | /* find the length of base64 data */ |
395 | 0 | pos = line->pos; |
396 | 0 | for (len = 0; line->pos < line->len; len++) { |
397 | 0 | if (buf_getbyte(line) == ' ') { |
398 | 0 | break; |
399 | 0 | } |
400 | 0 | } |
401 | | |
402 | | /* find out the length of the public key info, stop at the first space */ |
403 | 0 | infopos = line->pos; |
404 | 0 | for (infolen = 0; line->pos < line->len; infolen++) { |
405 | 0 | const char c = buf_getbyte(line); |
406 | 0 | if (c == ' ') { |
407 | 0 | break; |
408 | 0 | } |
409 | | /* We have an allowlist - authorized_keys lines can't be fully trusted, |
410 | | some shell scripts may do unsafe things with env var values */ |
411 | 0 | if (!(isalnum(c) || strchr(".,_-+@", c))) { |
412 | 0 | TRACE(("Not setting SSH_PUBKEYINFO, special characters")) |
413 | 0 | infolen = 0; |
414 | 0 | break; |
415 | 0 | } |
416 | 0 | } |
417 | 0 | if (infolen > 0) { |
418 | 0 | info_str = m_malloc(infolen + 1); |
419 | 0 | buf_setpos(line, infopos); |
420 | 0 | strncpy(info_str, buf_getptr(line, infolen), infolen); |
421 | 0 | } |
422 | | |
423 | | /* truncate to base64 data length */ |
424 | 0 | buf_setpos(line, pos); |
425 | 0 | buf_setlen(line, line->pos + len); |
426 | |
|
427 | 0 | TRACE(("checkpubkey_line: line pos = %d len = %d", line->pos, line->len)) |
428 | |
|
429 | 0 | ret = cmp_base64_key(keyblob, keybloblen, (const unsigned char *) algo, algolen, line, NULL); |
430 | | |
431 | | /* free pubkey_info if it is filled */ |
432 | 0 | if (ret == DROPBEAR_SUCCESS) { |
433 | 0 | #if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT |
434 | 0 | if (ret_options) { |
435 | 0 | *ret_options = svr_parse_pubkey_options(options_buf, line_num, filename); |
436 | 0 | if (*ret_options == NULL) { |
437 | 0 | ret = DROPBEAR_FAILURE; |
438 | 0 | goto out; |
439 | 0 | } |
440 | | /* take the (optional) public key information */ |
441 | 0 | (*ret_options)->info_env = info_str; |
442 | 0 | info_str = NULL; |
443 | 0 | } |
444 | 0 | #endif |
445 | 0 | } |
446 | | |
447 | 0 | out: |
448 | 0 | if (options_buf) { |
449 | 0 | buf_free(options_buf); |
450 | 0 | } |
451 | 0 | if (info_str) { |
452 | 0 | m_free(info_str); |
453 | 0 | } |
454 | 0 | return ret; |
455 | 0 | } |
456 | | |
457 | | /* Returns the full path to the user's authorized_keys file in an |
458 | | * allocated string which caller must free. */ |
459 | 0 | static char *authorized_keys_filepath() { |
460 | 0 | size_t len = 0; |
461 | 0 | char *pathname = NULL, *dir = NULL; |
462 | 0 | const char *filename = "authorized_keys"; |
463 | |
|
464 | 0 | dir = expand_homedir_path_home(svr_opts.authorized_keys_dir, |
465 | 0 | ses.authstate.pw_dir); |
466 | | |
467 | | /* allocate max required pathname storage, |
468 | | * = dir + "/" + "authorized_keys" + '\0' */; |
469 | 0 | len = strlen(dir) + strlen(filename) + 2; |
470 | 0 | pathname = m_malloc(len); |
471 | 0 | snprintf(pathname, len, "%s/%s", dir, filename); |
472 | 0 | m_free(dir); |
473 | 0 | return pathname; |
474 | 0 | } |
475 | | |
476 | | /* Checks whether a specified publickey (and associated algorithm) is an |
477 | | * acceptable key for authentication */ |
478 | | /* Returns DROPBEAR_SUCCESS if key is ok for auth, DROPBEAR_FAILURE otherwise */ |
479 | | static int checkpubkey(const char* keyalgo, unsigned int keyalgolen, |
480 | | const unsigned char* keyblob, unsigned int keybloblen, |
481 | 0 | struct PubKeyOptions **ret_options) { |
482 | |
|
483 | 0 | FILE * authfile = NULL; |
484 | 0 | char * filename = NULL; |
485 | 0 | int ret = DROPBEAR_FAILURE; |
486 | 0 | buffer * line = NULL; |
487 | 0 | int line_num; |
488 | 0 | uid_t origuid; |
489 | 0 | gid_t origgid; |
490 | |
|
491 | 0 | TRACE(("enter checkpubkey")) |
492 | |
|
493 | 0 | #if DROPBEAR_SVR_MULTIUSER |
494 | | /* access the file as the authenticating user. */ |
495 | 0 | origuid = getuid(); |
496 | 0 | origgid = getgid(); |
497 | 0 | if ((setegid(ses.authstate.pw_gid)) < 0 || |
498 | 0 | (seteuid(ses.authstate.pw_uid)) < 0) { |
499 | 0 | dropbear_exit("Failed to set euid"); |
500 | 0 | } |
501 | 0 | #endif |
502 | | /* check file permissions, also whether file exists */ |
503 | 0 | if (checkpubkeyperms() == DROPBEAR_FAILURE) { |
504 | 0 | TRACE(("bad authorized_keys permissions, or file doesn't exist")) |
505 | 0 | } else { |
506 | 0 | int fd; |
507 | | /* we don't need to check pw and pw_dir for validity, since |
508 | | * its been done in checkpubkeyperms. */ |
509 | 0 | filename = authorized_keys_filepath(); |
510 | 0 | fd = open(filename, O_RDONLY | O_NONBLOCK); |
511 | 0 | if (fd >= 0) { |
512 | 0 | authfile = fdopen(fd, "r"); |
513 | 0 | if (!authfile) { |
514 | | /* fdopen could fail with ENOMEM */ |
515 | 0 | m_close(fd); |
516 | 0 | } |
517 | 0 | } |
518 | 0 | if (!authfile) { |
519 | 0 | TRACE(("checkpubkey: failed opening %s: %s", filename, strerror(errno))) |
520 | 0 | } |
521 | 0 | } |
522 | 0 | #if DROPBEAR_SVR_MULTIUSER |
523 | 0 | if ((seteuid(origuid)) < 0 || |
524 | 0 | (setegid(origgid)) < 0) { |
525 | 0 | dropbear_exit("Failed to revert euid"); |
526 | 0 | } |
527 | 0 | #endif |
528 | | |
529 | 0 | if (authfile == NULL) { |
530 | 0 | goto out; |
531 | 0 | } |
532 | 0 | TRACE(("checkpubkey: opened authorized_keys OK")) |
533 | | |
534 | 0 | line = buf_new(MAX_AUTHKEYS_LINE); |
535 | 0 | line_num = 0; |
536 | | |
537 | | /* iterate through the lines */ |
538 | 0 | do { |
539 | 0 | if (buf_getline(line, authfile) == DROPBEAR_FAILURE) { |
540 | | /* EOF reached */ |
541 | 0 | TRACE(("checkpubkey: authorized_keys EOF reached")) |
542 | 0 | break; |
543 | 0 | } |
544 | 0 | line_num++; |
545 | |
|
546 | 0 | ret = checkpubkey_line(line, line_num, filename, keyalgo, keyalgolen, |
547 | 0 | keyblob, keybloblen, ret_options); |
548 | 0 | if (ret == DROPBEAR_SUCCESS) { |
549 | 0 | break; |
550 | 0 | } |
551 | | |
552 | | /* We continue to the next line otherwise */ |
553 | 0 | } while (1); |
554 | |
|
555 | 0 | out: |
556 | 0 | if (authfile) { |
557 | 0 | fclose(authfile); |
558 | 0 | } |
559 | 0 | if (line) { |
560 | 0 | buf_free(line); |
561 | 0 | } |
562 | 0 | m_free(filename); |
563 | 0 | TRACE(("leave checkpubkey: ret=%d", ret)) |
564 | 0 | return ret; |
565 | 0 | } |
566 | | |
567 | | |
568 | | /* Returns DROPBEAR_SUCCESS if file permissions for pubkeys are ok, |
569 | | * DROPBEAR_FAILURE otherwise. |
570 | | * Checks that the authorized_keys path permissions are all owned by either |
571 | | * root or the user, and are g-w, o-w. |
572 | | * When this path is inside the user's home dir it checks up to and including |
573 | | * the home dir, otherwise it checks every path component. */ |
574 | 0 | static int checkpubkeyperms() { |
575 | 0 | char *path = authorized_keys_filepath(), *sep = NULL; |
576 | 0 | int ret = DROPBEAR_SUCCESS; |
577 | |
|
578 | 0 | TRACE(("enter checkpubkeyperms")) |
579 | | |
580 | | /* Walk back up path checking permissions, stopping at either homedir, |
581 | | * or root if the path is outside of the homedir. */ |
582 | 0 | while ((sep = strrchr(path, '/')) != NULL) { |
583 | 0 | if (sep == path) { /* root directory */ |
584 | 0 | sep++; |
585 | 0 | } |
586 | 0 | *sep = '\0'; |
587 | 0 | if (checkfileperm(path) != DROPBEAR_SUCCESS) { |
588 | 0 | TRACE(("checkpubkeyperms: bad perm on %s", path)) |
589 | 0 | ret = DROPBEAR_FAILURE; |
590 | 0 | } |
591 | 0 | if (strcmp(path, ses.authstate.pw_dir) == 0 || strcmp(path, "/") == 0) { |
592 | 0 | break; |
593 | 0 | } |
594 | 0 | } |
595 | | |
596 | | /* all looks ok, return success */ |
597 | 0 | m_free(path); |
598 | |
|
599 | 0 | TRACE(("leave checkpubkeyperms")) |
600 | 0 | return ret; |
601 | 0 | } |
602 | | |
603 | | /* Checks that a file is owned by the user or root, and isn't writable by |
604 | | * group or other */ |
605 | | /* returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ |
606 | 0 | static int checkfileperm(char * filename) { |
607 | 0 | struct stat filestat; |
608 | 0 | int badperm = 0; |
609 | |
|
610 | 0 | TRACE(("enter checkfileperm(%s)", filename)) |
611 | |
|
612 | 0 | if (stat(filename, &filestat) != 0) { |
613 | 0 | TRACE(("leave checkfileperm: stat() != 0")) |
614 | 0 | return DROPBEAR_FAILURE; |
615 | 0 | } |
616 | | /* check ownership - user or root only*/ |
617 | 0 | if (filestat.st_uid != ses.authstate.pw_uid |
618 | 0 | && filestat.st_uid != 0) { |
619 | 0 | badperm = 1; |
620 | 0 | TRACE(("wrong ownership")) |
621 | 0 | } |
622 | | /* check permissions - don't want group or others +w */ |
623 | 0 | if (filestat.st_mode & (S_IWGRP | S_IWOTH)) { |
624 | 0 | badperm = 1; |
625 | 0 | TRACE(("wrong perms")) |
626 | 0 | } |
627 | 0 | if (badperm) { |
628 | 0 | if (!ses.authstate.perm_warn) { |
629 | 0 | ses.authstate.perm_warn = 1; |
630 | 0 | dropbear_log(LOG_INFO, "%s must be owned by user or root, and not writable by group or others", filename); |
631 | 0 | } |
632 | 0 | TRACE(("leave checkfileperm: failure perms/owner")) |
633 | 0 | return DROPBEAR_FAILURE; |
634 | 0 | } |
635 | | |
636 | 0 | TRACE(("leave checkfileperm: success")) |
637 | 0 | return DROPBEAR_SUCCESS; |
638 | 0 | } |
639 | | |
640 | | #if DROPBEAR_FUZZ |
641 | | void fuzz_checkpubkey_line(buffer* line, int line_num, char* filename, |
642 | | const char* algo, unsigned int algolen, |
643 | 0 | const unsigned char* keyblob, unsigned int keybloblen) { |
644 | | struct PubKeyOptions *options = NULL; |
645 | 0 | checkpubkey_line(line, line_num, filename, algo, algolen, keyblob, keybloblen, &options); |
646 | 0 | svr_pubkey_options_cleanup(options); |
647 | 0 | } |
648 | | #endif |
649 | | |
650 | | #endif |