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