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