Home | History | Annotate | Download | only in dropbear
      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 /* Process a pubkey auth request */
     26 
     27 #include "includes.h"
     28 #include "session.h"
     29 #include "dbutil.h"
     30 #include "buffer.h"
     31 #include "signkey.h"
     32 #include "auth.h"
     33 #include "ssh.h"
     34 #include "packet.h"
     35 #include "algo.h"
     36 
     37 #ifdef ENABLE_SVR_PUBKEY_AUTH
     38 
     39 #define MIN_AUTHKEYS_LINE 10 /* "ssh-rsa AB" - short but doesn't matter */
     40 #define MAX_AUTHKEYS_LINE 4200 /* max length of a line in authkeys */
     41 
     42 static int checkpubkey(unsigned char* algo, unsigned int algolen,
     43 		unsigned char* keyblob, unsigned int keybloblen);
     44 static int checkpubkeyperms();
     45 static void send_msg_userauth_pk_ok(unsigned char* algo, unsigned int algolen,
     46 		unsigned char* keyblob, unsigned int keybloblen);
     47 static int checkfileperm(char * filename);
     48 
     49 /* process a pubkey auth request, sending success or failure message as
     50  * appropriate */
     51 void svr_auth_pubkey() {
     52 
     53 	unsigned char testkey; /* whether we're just checking if a key is usable */
     54 	unsigned char* algo = NULL; /* pubkey algo */
     55 	unsigned int algolen;
     56 	unsigned char* keyblob = NULL;
     57 	unsigned int keybloblen;
     58 	buffer * signbuf = NULL;
     59 	sign_key * key = NULL;
     60 	char* fp = NULL;
     61 	int type = -1;
     62 
     63 	TRACE(("enter pubkeyauth"))
     64 
     65 	/* 0 indicates user just wants to check if key can be used, 1 is an
     66 	 * actual attempt*/
     67 	testkey = (buf_getbool(ses.payload) == 0);
     68 
     69 	algo = buf_getstring(ses.payload, &algolen);
     70 	keybloblen = buf_getint(ses.payload);
     71 	keyblob = buf_getptr(ses.payload, keybloblen);
     72 
     73 	/* check if the key is valid */
     74 	if (checkpubkey(algo, algolen, keyblob, keybloblen) == DROPBEAR_FAILURE) {
     75 		send_msg_userauth_failure(0, 0);
     76 		goto out;
     77 	}
     78 
     79 	/* let them know that the key is ok to use */
     80 	if (testkey) {
     81 		send_msg_userauth_pk_ok(algo, algolen, keyblob, keybloblen);
     82 		goto out;
     83 	}
     84 
     85 	/* now we can actually verify the signature */
     86 
     87 	/* get the key */
     88 	key = new_sign_key();
     89 	type = DROPBEAR_SIGNKEY_ANY;
     90 	if (buf_get_pub_key(ses.payload, key, &type) == DROPBEAR_FAILURE) {
     91 		send_msg_userauth_failure(0, 1);
     92 		goto out;
     93 	}
     94 
     95 	/* create the data which has been signed - this a string containing
     96 	 * session_id, concatenated with the payload packet up to the signature */
     97 	signbuf = buf_new(ses.payload->pos + 4 + SHA1_HASH_SIZE);
     98 	buf_putstring(signbuf, ses.session_id, SHA1_HASH_SIZE);
     99 	buf_putbytes(signbuf, ses.payload->data, ses.payload->pos);
    100 	buf_setpos(signbuf, 0);
    101 
    102 	/* ... and finally verify the signature */
    103 	fp = sign_key_fingerprint(keyblob, keybloblen);
    104 	if (buf_verify(ses.payload, key, buf_getptr(signbuf, signbuf->len),
    105 				signbuf->len) == DROPBEAR_SUCCESS) {
    106 		dropbear_log(LOG_NOTICE,
    107 				"pubkey auth succeeded for '%s' with key %s from %s",
    108 				ses.authstate.printableuser, fp, svr_ses.addrstring);
    109 		send_msg_userauth_success();
    110 	} else {
    111 		dropbear_log(LOG_WARNING,
    112 				"pubkey auth bad signature for '%s' with key %s from %s",
    113 				ses.authstate.printableuser, fp, svr_ses.addrstring);
    114 		send_msg_userauth_failure(0, 1);
    115 	}
    116 	m_free(fp);
    117 
    118 out:
    119 	/* cleanup stuff */
    120 	if (signbuf) {
    121 		buf_free(signbuf);
    122 	}
    123 	if (algo) {
    124 		m_free(algo);
    125 	}
    126 	if (key) {
    127 		sign_key_free(key);
    128 		key = NULL;
    129 	}
    130 	TRACE(("leave pubkeyauth"))
    131 }
    132 
    133 /* Reply that the key is valid for auth, this is sent when the user sends
    134  * a straight copy of their pubkey to test, to avoid having to perform
    135  * expensive signing operations with a worthless key */
    136 static void send_msg_userauth_pk_ok(unsigned char* algo, unsigned int algolen,
    137 		unsigned char* keyblob, unsigned int keybloblen) {
    138 
    139 	TRACE(("enter send_msg_userauth_pk_ok"))
    140 	CHECKCLEARTOWRITE();
    141 
    142 	buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_PK_OK);
    143 	buf_putstring(ses.writepayload, algo, algolen);
    144 	buf_putstring(ses.writepayload, keyblob, keybloblen);
    145 
    146 	encrypt_packet();
    147 	TRACE(("leave send_msg_userauth_pk_ok"))
    148 
    149 }
    150 
    151 /* Checks whether a specified publickey (and associated algorithm) is an
    152  * acceptable key for authentication */
    153 /* Returns DROPBEAR_SUCCESS if key is ok for auth, DROPBEAR_FAILURE otherwise */
    154 static int checkpubkey(unsigned char* algo, unsigned int algolen,
    155 		unsigned char* keyblob, unsigned int keybloblen) {
    156 
    157 	FILE * authfile = NULL;
    158 	char * filename = NULL;
    159 	int ret = DROPBEAR_FAILURE;
    160 	buffer * line = NULL;
    161 	unsigned int len, pos;
    162 
    163 	TRACE(("enter checkpubkey"))
    164 
    165 	/* check that we can use the algo */
    166 	if (have_algo(algo, algolen, sshhostkey) == DROPBEAR_FAILURE) {
    167 		dropbear_log(LOG_WARNING,
    168 				"pubkey auth attempt with unknown algo for '%s' from %s",
    169 				ses.authstate.printableuser, svr_ses.addrstring);
    170 		goto out;
    171 	}
    172 
    173 	/* check file permissions, also whether file exists */
    174 	if (checkpubkeyperms() == DROPBEAR_FAILURE) {
    175 		TRACE(("bad authorized_keys permissions, or file doesn't exist"))
    176 		goto out;
    177 	}
    178 
    179 	/* we don't need to check pw and pw_dir for validity, since
    180 	 * its been done in checkpubkeyperms. */
    181 	len = strlen(ses.authstate.pw->pw_dir);
    182 	/* allocate max required pathname storage,
    183 	 * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */
    184 	filename = m_malloc(len + 22);
    185 	snprintf(filename, len + 22, "%s/.ssh/authorized_keys",
    186 				ses.authstate.pw->pw_dir);
    187 
    188 	/* open the file */
    189 	authfile = fopen(filename, "r");
    190 	if (authfile == NULL) {
    191 		goto out;
    192 	}
    193 	TRACE(("checkpubkey: opened authorized_keys OK"))
    194 
    195 	line = buf_new(MAX_AUTHKEYS_LINE);
    196 
    197 	/* iterate through the lines */
    198 	do {
    199 
    200 		if (buf_getline(line, authfile) == DROPBEAR_FAILURE) {
    201 			/* EOF reached */
    202 			TRACE(("checkpubkey: authorized_keys EOF reached"))
    203 			break;
    204 		}
    205 
    206 		if (line->len < MIN_AUTHKEYS_LINE) {
    207 			TRACE(("checkpubkey: line too short"))
    208 			continue; /* line is too short for it to be a valid key */
    209 		}
    210 
    211 		/* check the key type - this also stops us from using keys
    212 		 * which have options with them */
    213 		if (strncmp(buf_getptr(line, algolen), algo, algolen) != 0) {
    214 			continue;
    215 		}
    216 		buf_incrpos(line, algolen);
    217 
    218 		/* check for space (' ') character */
    219 		if (buf_getbyte(line) != ' ') {
    220 			TRACE(("checkpubkey: space character expected, isn't there"))
    221 			continue;
    222 		}
    223 
    224 		/* truncate the line at the space after the base64 data */
    225 		pos = line->pos;
    226 		for (len = 0; line->pos < line->len; len++) {
    227 			if (buf_getbyte(line) == ' ') break;
    228 		}
    229 		buf_setpos(line, pos);
    230 		buf_setlen(line, line->pos + len);
    231 
    232 		TRACE(("checkpubkey: line pos = %d len = %d", line->pos, line->len))
    233 
    234 		ret = cmp_base64_key(keyblob, keybloblen, algo, algolen, line, NULL);
    235 		if (ret == DROPBEAR_SUCCESS) {
    236 			break;
    237 		}
    238 
    239 		/* We continue to the next line otherwise */
    240 
    241 	} while (1);
    242 
    243 out:
    244 	if (authfile) {
    245 		fclose(authfile);
    246 	}
    247 	if (line) {
    248 		buf_free(line);
    249 	}
    250 	m_free(filename);
    251 	TRACE(("leave checkpubkey: ret=%d", ret))
    252 	return ret;
    253 }
    254 
    255 
    256 /* Returns DROPBEAR_SUCCESS if file permissions for pubkeys are ok,
    257  * DROPBEAR_FAILURE otherwise.
    258  * Checks that the user's homedir, ~/.ssh, and
    259  * ~/.ssh/authorized_keys are all owned by either root or the user, and are
    260  * g-w, o-w */
    261 static int checkpubkeyperms() {
    262 
    263 	char* filename = NULL;
    264 	int ret = DROPBEAR_FAILURE;
    265 	unsigned int len;
    266 
    267 	TRACE(("enter checkpubkeyperms"))
    268 
    269 	if (ses.authstate.pw->pw_dir == NULL) {
    270 		goto out;
    271 	}
    272 
    273 	if ((len = strlen(ses.authstate.pw->pw_dir)) == 0) {
    274 		goto out;
    275 	}
    276 
    277 	/* allocate max required pathname storage,
    278 	 * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */
    279 	filename = m_malloc(len + 22);
    280 	strncpy(filename, ses.authstate.pw->pw_dir, len+1);
    281 
    282 	/* check ~ */
    283 	if (checkfileperm(filename) != DROPBEAR_SUCCESS) {
    284 		goto out;
    285 	}
    286 
    287 	/* check ~/.ssh */
    288 	strncat(filename, "/.ssh", 5); /* strlen("/.ssh") == 5 */
    289 	if (checkfileperm(filename) != DROPBEAR_SUCCESS) {
    290 		goto out;
    291 	}
    292 
    293 	/* now check ~/.ssh/authorized_keys */
    294 	strncat(filename, "/authorized_keys", 16);
    295 	if (checkfileperm(filename) != DROPBEAR_SUCCESS) {
    296 		goto out;
    297 	}
    298 
    299 	/* file looks ok, return success */
    300 	ret = DROPBEAR_SUCCESS;
    301 
    302 out:
    303 	m_free(filename);
    304 
    305 	TRACE(("leave checkpubkeyperms"))
    306 	return ret;
    307 }
    308 
    309 /* Checks that a file is owned by the user or root, and isn't writable by
    310  * group or other */
    311 /* returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
    312 static int checkfileperm(char * filename) {
    313 	struct stat filestat;
    314 	int badperm = 0;
    315 
    316 	TRACE(("enter checkfileperm(%s)", filename))
    317 
    318 	if (stat(filename, &filestat) != 0) {
    319 		TRACE(("leave checkfileperm: stat() != 0"))
    320 		return DROPBEAR_FAILURE;
    321 	}
    322 	/* check ownership - user or root only*/
    323 	if (filestat.st_uid != ses.authstate.pw->pw_uid
    324 			&& filestat.st_uid != 0) {
    325 		badperm = 1;
    326 		TRACE(("wrong ownership"))
    327 	}
    328 	/* check permissions - don't want group or others +w */
    329 	if (filestat.st_mode & (S_IWGRP | S_IWOTH)) {
    330 		badperm = 1;
    331 		TRACE(("wrong perms"))
    332 	}
    333 	if (badperm) {
    334 		if (!ses.authstate.perm_warn) {
    335 			ses.authstate.perm_warn = 1;
    336 			dropbear_log(LOG_INFO, "%s must be owned by user or root, and not writable by others", filename);
    337 		}
    338 		TRACE(("leave checkfileperm: failure perms/owner"))
    339 		return DROPBEAR_FAILURE;
    340 	}
    341 
    342 	TRACE(("leave checkfileperm: success"))
    343 	return DROPBEAR_SUCCESS;
    344 }
    345 
    346 
    347 #endif
    348