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