1 /* 2 * Dropbear - a SSH2 server 3 * 4 * Copyright (c) 2002-2004 Matt Johnston 5 * Copyright (c) 2004 by Mihnea Stoenescu 6 * All rights reserved. 7 * 8 * Permission is hereby granted, free of charge, to any person obtaining a copy 9 * of this software and associated documentation files (the "Software"), to deal 10 * in the Software without restriction, including without limitation the rights 11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 * copies of the Software, and to permit persons to whom the Software is 13 * furnished to do so, subject to the following conditions: 14 * 15 * The above copyright notice and this permission notice shall be included in 16 * all copies or substantial portions of the Software. 17 * 18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 * SOFTWARE. */ 25 26 #include "includes.h" 27 #include "session.h" 28 #include "dbutil.h" 29 #include "algo.h" 30 #include "buffer.h" 31 #include "session.h" 32 #include "kex.h" 33 #include "ssh.h" 34 #include "packet.h" 35 #include "bignum.h" 36 #include "random.h" 37 #include "runopts.h" 38 #include "signkey.h" 39 40 41 static void checkhostkey(unsigned char* keyblob, unsigned int keybloblen); 42 #define MAX_KNOWNHOSTS_LINE 4500 43 44 void send_msg_kexdh_init() { 45 46 cli_ses.dh_e = (mp_int*)m_malloc(sizeof(mp_int)); 47 cli_ses.dh_x = (mp_int*)m_malloc(sizeof(mp_int)); 48 m_mp_init_multi(cli_ses.dh_e, cli_ses.dh_x, NULL); 49 50 gen_kexdh_vals(cli_ses.dh_e, cli_ses.dh_x); 51 52 CHECKCLEARTOWRITE(); 53 buf_putbyte(ses.writepayload, SSH_MSG_KEXDH_INIT); 54 buf_putmpint(ses.writepayload, cli_ses.dh_e); 55 encrypt_packet(); 56 ses.requirenext = SSH_MSG_KEXDH_REPLY; 57 } 58 59 /* Handle a diffie-hellman key exchange reply. */ 60 void recv_msg_kexdh_reply() { 61 62 DEF_MP_INT(dh_f); 63 sign_key *hostkey = NULL; 64 unsigned int type, keybloblen; 65 unsigned char* keyblob = NULL; 66 67 68 TRACE(("enter recv_msg_kexdh_reply")) 69 70 if (cli_ses.kex_state != KEXDH_INIT_SENT) { 71 dropbear_exit("Received out-of-order kexdhreply"); 72 } 73 m_mp_init(&dh_f); 74 type = ses.newkeys->algo_hostkey; 75 TRACE(("type is %d", type)) 76 77 hostkey = new_sign_key(); 78 keybloblen = buf_getint(ses.payload); 79 80 keyblob = buf_getptr(ses.payload, keybloblen); 81 if (!ses.kexstate.donefirstkex) { 82 /* Only makes sense the first time */ 83 checkhostkey(keyblob, keybloblen); 84 } 85 86 if (buf_get_pub_key(ses.payload, hostkey, &type) != DROPBEAR_SUCCESS) { 87 TRACE(("failed getting pubkey")) 88 dropbear_exit("Bad KEX packet"); 89 } 90 91 if (buf_getmpint(ses.payload, &dh_f) != DROPBEAR_SUCCESS) { 92 TRACE(("failed getting mpint")) 93 dropbear_exit("Bad KEX packet"); 94 } 95 96 kexdh_comb_key(cli_ses.dh_e, cli_ses.dh_x, &dh_f, hostkey); 97 mp_clear(&dh_f); 98 mp_clear_multi(cli_ses.dh_e, cli_ses.dh_x, NULL); 99 m_free(cli_ses.dh_e); 100 m_free(cli_ses.dh_x); 101 102 if (buf_verify(ses.payload, hostkey, ses.hash, SHA1_HASH_SIZE) 103 != DROPBEAR_SUCCESS) { 104 dropbear_exit("Bad hostkey signature"); 105 } 106 107 sign_key_free(hostkey); 108 hostkey = NULL; 109 110 send_msg_newkeys(); 111 ses.requirenext = SSH_MSG_NEWKEYS; 112 TRACE(("leave recv_msg_kexdh_init")) 113 } 114 115 static void ask_to_confirm(unsigned char* keyblob, unsigned int keybloblen) { 116 117 char* fp = NULL; 118 FILE *tty = NULL; 119 char response = 'z'; 120 121 fp = sign_key_fingerprint(keyblob, keybloblen); 122 if (cli_opts.always_accept_key) { 123 fprintf(stderr, "\nHost '%s' key accepted unconditionally.\n(fingerprint %s)\n", 124 cli_opts.remotehost, 125 fp); 126 m_free(fp); 127 return; 128 } 129 fprintf(stderr, "\nHost '%s' is not in the trusted hosts file.\n(fingerprint %s)\nDo you want to continue connecting? (y/n)\n", 130 cli_opts.remotehost, 131 fp); 132 m_free(fp); 133 134 tty = fopen(_PATH_TTY, "r"); 135 if (tty) { 136 response = getc(tty); 137 fclose(tty); 138 } else { 139 response = getc(stdin); 140 } 141 142 if (response == 'y') { 143 return; 144 } 145 146 dropbear_exit("Didn't validate host key"); 147 } 148 149 static FILE* open_known_hosts_file(int * readonly) 150 { 151 FILE * hostsfile = NULL; 152 char * filename = NULL; 153 char * homedir = NULL; 154 155 homedir = getenv("HOME"); 156 157 if (!homedir) { 158 struct passwd * pw = NULL; 159 pw = getpwuid(getuid()); 160 if (pw) { 161 homedir = pw->pw_dir; 162 } 163 } 164 165 if (homedir) { 166 unsigned int len; 167 len = strlen(homedir); 168 filename = m_malloc(len + 18); /* "/.ssh/known_hosts" and null-terminator*/ 169 170 snprintf(filename, len+18, "%s/.ssh", homedir); 171 /* Check that ~/.ssh exists - easiest way is just to mkdir */ 172 if (mkdir(filename, S_IRWXU) != 0) { 173 if (errno != EEXIST) { 174 dropbear_log(LOG_INFO, "Warning: failed creating %s/.ssh: %s", 175 homedir, strerror(errno)); 176 TRACE(("mkdir didn't work: %s", strerror(errno))) 177 goto out; 178 } 179 } 180 181 snprintf(filename, len+18, "%s/.ssh/known_hosts", homedir); 182 hostsfile = fopen(filename, "a+"); 183 184 if (hostsfile != NULL) { 185 *readonly = 0; 186 fseek(hostsfile, 0, SEEK_SET); 187 } else { 188 /* We mightn't have been able to open it if it was read-only */ 189 if (errno == EACCES || errno == EROFS) { 190 TRACE(("trying readonly: %s", strerror(errno))) 191 *readonly = 1; 192 hostsfile = fopen(filename, "r"); 193 } 194 } 195 } 196 197 if (hostsfile == NULL) { 198 TRACE(("hostsfile didn't open: %s", strerror(errno))) 199 dropbear_log(LOG_WARNING, "Failed to open %s/.ssh/known_hosts", 200 homedir); 201 goto out; 202 } 203 204 out: 205 m_free(filename); 206 return hostsfile; 207 } 208 209 static void checkhostkey(unsigned char* keyblob, unsigned int keybloblen) { 210 211 FILE *hostsfile = NULL; 212 int readonly = 0; 213 unsigned int hostlen, algolen; 214 unsigned long len; 215 const char *algoname = NULL; 216 char * fingerprint = NULL; 217 buffer * line = NULL; 218 int ret; 219 220 hostsfile = open_known_hosts_file(&readonly); 221 if (!hostsfile) { 222 ask_to_confirm(keyblob, keybloblen); 223 /* ask_to_confirm will exit upon failure */ 224 return; 225 } 226 227 line = buf_new(MAX_KNOWNHOSTS_LINE); 228 hostlen = strlen(cli_opts.remotehost); 229 algoname = signkey_name_from_type(ses.newkeys->algo_hostkey, &algolen); 230 231 do { 232 if (buf_getline(line, hostsfile) == DROPBEAR_FAILURE) { 233 TRACE(("failed reading line: prob EOF")) 234 break; 235 } 236 237 /* The line is too short to be sensible */ 238 /* "30" is 'enough to hold ssh-dss plus the spaces, ie so we don't 239 * buf_getfoo() past the end and die horribly - the base64 parsing 240 * code is what tiptoes up to the end nicely */ 241 if (line->len < (hostlen+30) ) { 242 TRACE(("line is too short to be sensible")) 243 continue; 244 } 245 246 /* Compare hostnames */ 247 if (strncmp(cli_opts.remotehost, buf_getptr(line, hostlen), 248 hostlen) != 0) { 249 TRACE(("hosts don't match")) 250 continue; 251 } 252 253 buf_incrpos(line, hostlen); 254 if (buf_getbyte(line) != ' ') { 255 /* there wasn't a space after the hostname, something dodgy */ 256 TRACE(("missing space afte matching hostname")) 257 continue; 258 } 259 260 if (strncmp(buf_getptr(line, algolen), algoname, algolen) != 0) { 261 TRACE(("algo doesn't match")) 262 continue; 263 } 264 265 buf_incrpos(line, algolen); 266 if (buf_getbyte(line) != ' ') { 267 TRACE(("missing space after algo")) 268 continue; 269 } 270 271 /* Now we're at the interesting hostkey */ 272 ret = cmp_base64_key(keyblob, keybloblen, algoname, algolen, 273 line, &fingerprint); 274 275 if (ret == DROPBEAR_SUCCESS) { 276 /* Good matching key */ 277 TRACE(("good matching key")) 278 goto out; 279 } 280 281 /* The keys didn't match. eep. Note that we're "leaking" 282 the fingerprint strings here, but we're exiting anyway */ 283 dropbear_exit("\n\nHost key mismatch for %s !\n" 284 "Fingerprint is %s\n" 285 "Expected %s\n" 286 "If you know that the host key is correct you can\nremove the bad entry from ~/.ssh/known_hosts", 287 cli_opts.remotehost, 288 sign_key_fingerprint(keyblob, keybloblen), 289 fingerprint ? fingerprint : "UNKNOWN"); 290 } while (1); /* keep going 'til something happens */ 291 292 /* Key doesn't exist yet */ 293 ask_to_confirm(keyblob, keybloblen); 294 295 /* If we get here, they said yes */ 296 297 if (readonly) { 298 TRACE(("readonly")) 299 goto out; 300 } 301 302 if (!cli_opts.always_accept_key) { 303 /* put the new entry in the file */ 304 fseek(hostsfile, 0, SEEK_END); /* In case it wasn't opened append */ 305 buf_setpos(line, 0); 306 buf_setlen(line, 0); 307 buf_putbytes(line, ses.remotehost, hostlen); 308 buf_putbyte(line, ' '); 309 buf_putbytes(line, algoname, algolen); 310 buf_putbyte(line, ' '); 311 len = line->size - line->pos; 312 TRACE(("keybloblen %d, len %d", keybloblen, len)) 313 /* The only failure with base64 is buffer_overflow, but buf_getwriteptr 314 * will die horribly in the case anyway */ 315 base64_encode(keyblob, keybloblen, buf_getwriteptr(line, len), &len); 316 buf_incrwritepos(line, len); 317 buf_putbyte(line, '\n'); 318 buf_setpos(line, 0); 319 fwrite(buf_getptr(line, line->len), line->len, 1, hostsfile); 320 /* We ignore errors, since there's not much we can do about them */ 321 } 322 323 out: 324 if (hostsfile != NULL) { 325 fclose(hostsfile); 326 } 327 if (line != NULL) { 328 buf_free(line); 329 } 330 } 331