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