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