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