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