cli-kex.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  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. #include "includes.h"
  26. #include "session.h"
  27. #include "dbutil.h"
  28. #include "algo.h"
  29. #include "buffer.h"
  30. #include "session.h"
  31. #include "kex.h"
  32. #include "ssh.h"
  33. #include "packet.h"
  34. #include "bignum.h"
  35. #include "dbrandom.h"
  36. #include "runopts.h"
  37. #include "signkey.h"
  38. #include "ecc.h"
  39. static void checkhostkey(const unsigned char* keyblob, unsigned int keybloblen);
  40. #define MAX_KNOWNHOSTS_LINE 4500
  41. void send_msg_kexdh_init() {
  42. TRACE(("send_msg_kexdh_init()"))
  43. CHECKCLEARTOWRITE();
  44. #if DROPBEAR_FUZZ
  45. if (fuzz.fuzzing && fuzz.skip_kexmaths) {
  46. return;
  47. }
  48. #endif
  49. buf_putbyte(ses.writepayload, SSH_MSG_KEXDH_INIT);
  50. switch (ses.newkeys->algo_kex->mode) {
  51. #if DROPBEAR_NORMAL_DH
  52. case DROPBEAR_KEX_NORMAL_DH:
  53. if (ses.newkeys->algo_kex != cli_ses.param_kex_algo
  54. || !cli_ses.dh_param) {
  55. if (cli_ses.dh_param) {
  56. free_kexdh_param(cli_ses.dh_param);
  57. }
  58. cli_ses.dh_param = gen_kexdh_param();
  59. }
  60. buf_putmpint(ses.writepayload, &cli_ses.dh_param->pub);
  61. break;
  62. #endif
  63. #if DROPBEAR_ECDH
  64. case DROPBEAR_KEX_ECDH:
  65. if (ses.newkeys->algo_kex != cli_ses.param_kex_algo
  66. || !cli_ses.ecdh_param) {
  67. if (cli_ses.ecdh_param) {
  68. free_kexecdh_param(cli_ses.ecdh_param);
  69. }
  70. cli_ses.ecdh_param = gen_kexecdh_param();
  71. }
  72. buf_put_ecc_raw_pubkey_string(ses.writepayload, &cli_ses.ecdh_param->key);
  73. break;
  74. #endif
  75. #if DROPBEAR_CURVE25519
  76. case DROPBEAR_KEX_CURVE25519:
  77. if (ses.newkeys->algo_kex != cli_ses.param_kex_algo
  78. || !cli_ses.curve25519_param) {
  79. if (cli_ses.curve25519_param) {
  80. free_kexcurve25519_param(cli_ses.curve25519_param);
  81. }
  82. cli_ses.curve25519_param = gen_kexcurve25519_param();
  83. }
  84. buf_putstring(ses.writepayload, cli_ses.curve25519_param->pub, CURVE25519_LEN);
  85. break;
  86. #endif
  87. }
  88. cli_ses.param_kex_algo = ses.newkeys->algo_kex;
  89. encrypt_packet();
  90. }
  91. /* Handle a diffie-hellman key exchange reply. */
  92. void recv_msg_kexdh_reply() {
  93. sign_key *hostkey = NULL;
  94. unsigned int keytype, keybloblen;
  95. unsigned char* keyblob = NULL;
  96. TRACE(("enter recv_msg_kexdh_reply"))
  97. #if DROPBEAR_FUZZ
  98. if (fuzz.fuzzing && fuzz.skip_kexmaths) {
  99. return;
  100. }
  101. #endif
  102. if (cli_ses.kex_state != KEXDH_INIT_SENT) {
  103. dropbear_exit("Received out-of-order kexdhreply");
  104. }
  105. keytype = ses.newkeys->algo_hostkey;
  106. TRACE(("keytype is %d", keytype))
  107. hostkey = new_sign_key();
  108. keybloblen = buf_getint(ses.payload);
  109. keyblob = buf_getptr(ses.payload, keybloblen);
  110. if (!ses.kexstate.donefirstkex) {
  111. /* Only makes sense the first time */
  112. checkhostkey(keyblob, keybloblen);
  113. }
  114. if (buf_get_pub_key(ses.payload, hostkey, &keytype) != DROPBEAR_SUCCESS) {
  115. TRACE(("failed getting pubkey"))
  116. dropbear_exit("Bad KEX packet");
  117. }
  118. switch (ses.newkeys->algo_kex->mode) {
  119. #if DROPBEAR_NORMAL_DH
  120. case DROPBEAR_KEX_NORMAL_DH:
  121. {
  122. DEF_MP_INT(dh_f);
  123. m_mp_init(&dh_f);
  124. if (buf_getmpint(ses.payload, &dh_f) != DROPBEAR_SUCCESS) {
  125. TRACE(("failed getting mpint"))
  126. dropbear_exit("Bad KEX packet");
  127. }
  128. kexdh_comb_key(cli_ses.dh_param, &dh_f, hostkey);
  129. mp_clear(&dh_f);
  130. }
  131. break;
  132. #endif
  133. #if DROPBEAR_ECDH
  134. case DROPBEAR_KEX_ECDH:
  135. {
  136. buffer *ecdh_qs = buf_getstringbuf(ses.payload);
  137. kexecdh_comb_key(cli_ses.ecdh_param, ecdh_qs, hostkey);
  138. buf_free(ecdh_qs);
  139. }
  140. break;
  141. #endif
  142. #if DROPBEAR_CURVE25519
  143. case DROPBEAR_KEX_CURVE25519:
  144. {
  145. buffer *ecdh_qs = buf_getstringbuf(ses.payload);
  146. kexcurve25519_comb_key(cli_ses.curve25519_param, ecdh_qs, hostkey);
  147. buf_free(ecdh_qs);
  148. }
  149. break;
  150. #endif
  151. }
  152. #if DROPBEAR_NORMAL_DH
  153. if (cli_ses.dh_param) {
  154. free_kexdh_param(cli_ses.dh_param);
  155. cli_ses.dh_param = NULL;
  156. }
  157. #endif
  158. #if DROPBEAR_ECDH
  159. if (cli_ses.ecdh_param) {
  160. free_kexecdh_param(cli_ses.ecdh_param);
  161. cli_ses.ecdh_param = NULL;
  162. }
  163. #endif
  164. #if DROPBEAR_CURVE25519
  165. if (cli_ses.curve25519_param) {
  166. free_kexcurve25519_param(cli_ses.curve25519_param);
  167. cli_ses.curve25519_param = NULL;
  168. }
  169. #endif
  170. cli_ses.param_kex_algo = NULL;
  171. if (buf_verify(ses.payload, hostkey, ses.newkeys->algo_signature,
  172. ses.hash) != DROPBEAR_SUCCESS) {
  173. dropbear_exit("Bad hostkey signature");
  174. }
  175. sign_key_free(hostkey);
  176. hostkey = NULL;
  177. send_msg_newkeys();
  178. ses.requirenext = SSH_MSG_NEWKEYS;
  179. TRACE(("leave recv_msg_kexdh_init"))
  180. }
  181. static void ask_to_confirm(const unsigned char* keyblob, unsigned int keybloblen,
  182. const char* algoname) {
  183. char* fp = NULL;
  184. FILE *tty = NULL;
  185. int response = 'z';
  186. fp = sign_key_fingerprint(keyblob, keybloblen);
  187. if (cli_opts.always_accept_key) {
  188. dropbear_log(LOG_INFO, "\nHost '%s' key accepted unconditionally.\n(%s fingerprint %s)\n",
  189. cli_opts.remotehost,
  190. algoname,
  191. fp);
  192. m_free(fp);
  193. return;
  194. }
  195. fprintf(stderr, "\nHost '%s' is not in the trusted hosts file.\n(%s fingerprint %s)\nDo you want to continue connecting? (y/n) ",
  196. cli_opts.remotehost,
  197. algoname,
  198. fp);
  199. m_free(fp);
  200. tty = fopen(_PATH_TTY, "r");
  201. if (tty) {
  202. response = getc(tty);
  203. fclose(tty);
  204. } else {
  205. response = getc(stdin);
  206. }
  207. if (response == 'y') {
  208. return;
  209. }
  210. dropbear_exit("Didn't validate host key");
  211. }
  212. static FILE* open_known_hosts_file(int * readonly)
  213. {
  214. FILE * hostsfile = NULL;
  215. char * filename = NULL;
  216. char * homedir = NULL;
  217. homedir = getenv("HOME");
  218. if (!homedir) {
  219. struct passwd * pw = NULL;
  220. pw = getpwuid(getuid());
  221. if (pw) {
  222. homedir = pw->pw_dir;
  223. }
  224. }
  225. if (homedir) {
  226. unsigned int len;
  227. len = strlen(homedir);
  228. filename = m_malloc(len + 18); /* "/.ssh/known_hosts" and null-terminator*/
  229. snprintf(filename, len+18, "%s/.ssh", homedir);
  230. /* Check that ~/.ssh exists - easiest way is just to mkdir */
  231. if (mkdir(filename, S_IRWXU) != 0) {
  232. if (errno != EEXIST) {
  233. dropbear_log(LOG_INFO, "Warning: failed creating %s/.ssh: %s",
  234. homedir, strerror(errno));
  235. TRACE(("mkdir didn't work: %s", strerror(errno)))
  236. goto out;
  237. }
  238. }
  239. snprintf(filename, len+18, "%s/.ssh/known_hosts", homedir);
  240. hostsfile = fopen(filename, "a+");
  241. if (hostsfile != NULL) {
  242. *readonly = 0;
  243. fseek(hostsfile, 0, SEEK_SET);
  244. } else {
  245. /* We mightn't have been able to open it if it was read-only */
  246. if (errno == EACCES || errno == EROFS) {
  247. TRACE(("trying readonly: %s", strerror(errno)))
  248. *readonly = 1;
  249. hostsfile = fopen(filename, "r");
  250. }
  251. }
  252. }
  253. if (hostsfile == NULL) {
  254. TRACE(("hostsfile didn't open: %s", strerror(errno)))
  255. dropbear_log(LOG_WARNING, "Failed to open %s/.ssh/known_hosts",
  256. homedir);
  257. goto out;
  258. }
  259. out:
  260. m_free(filename);
  261. return hostsfile;
  262. }
  263. static void checkhostkey(const unsigned char* keyblob, unsigned int keybloblen) {
  264. FILE *hostsfile = NULL;
  265. int readonly = 0;
  266. unsigned int hostlen, algolen;
  267. unsigned long len;
  268. const char *algoname = NULL;
  269. char * fingerprint = NULL;
  270. buffer * line = NULL;
  271. int ret;
  272. if (cli_opts.no_hostkey_check) {
  273. dropbear_log(LOG_INFO, "Caution, skipping hostkey check for %s\n", cli_opts.remotehost);
  274. return;
  275. }
  276. algoname = signkey_name_from_type(ses.newkeys->algo_hostkey, &algolen);
  277. hostsfile = open_known_hosts_file(&readonly);
  278. if (!hostsfile) {
  279. ask_to_confirm(keyblob, keybloblen, algoname);
  280. /* ask_to_confirm will exit upon failure */
  281. return;
  282. }
  283. line = buf_new(MAX_KNOWNHOSTS_LINE);
  284. hostlen = strlen(cli_opts.remotehost);
  285. do {
  286. if (buf_getline(line, hostsfile) == DROPBEAR_FAILURE) {
  287. TRACE(("failed reading line: prob EOF"))
  288. break;
  289. }
  290. /* The line is too short to be sensible */
  291. /* "30" is 'enough to hold ssh-dss plus the spaces, ie so we don't
  292. * buf_getfoo() past the end and die horribly - the base64 parsing
  293. * code is what tiptoes up to the end nicely */
  294. if (line->len < (hostlen+30) ) {
  295. TRACE(("line is too short to be sensible"))
  296. continue;
  297. }
  298. /* Compare hostnames */
  299. if (strncmp(cli_opts.remotehost, (const char *) buf_getptr(line, hostlen),
  300. hostlen) != 0) {
  301. continue;
  302. }
  303. buf_incrpos(line, hostlen);
  304. if (buf_getbyte(line) != ' ') {
  305. /* there wasn't a space after the hostname, something dodgy */
  306. TRACE(("missing space afte matching hostname"))
  307. continue;
  308. }
  309. if (strncmp((const char *) buf_getptr(line, algolen), algoname, algolen) != 0) {
  310. TRACE(("algo doesn't match"))
  311. continue;
  312. }
  313. buf_incrpos(line, algolen);
  314. if (buf_getbyte(line) != ' ') {
  315. TRACE(("missing space after algo"))
  316. continue;
  317. }
  318. /* Now we're at the interesting hostkey */
  319. ret = cmp_base64_key(keyblob, keybloblen, (const unsigned char *) algoname, algolen,
  320. line, &fingerprint);
  321. if (ret == DROPBEAR_SUCCESS) {
  322. /* Good matching key */
  323. DEBUG1(("server match %s", fingerprint))
  324. goto out;
  325. }
  326. /* The keys didn't match. eep. Note that we're "leaking"
  327. the fingerprint strings here, but we're exiting anyway */
  328. dropbear_exit("\n\n%s host key mismatch for %s !\n"
  329. "Fingerprint is %s\n"
  330. "Expected %s\n"
  331. "If you know that the host key is correct you can\nremove the bad entry from ~/.ssh/known_hosts",
  332. algoname,
  333. cli_opts.remotehost,
  334. sign_key_fingerprint(keyblob, keybloblen),
  335. fingerprint ? fingerprint : "UNKNOWN");
  336. } while (1); /* keep going 'til something happens */
  337. /* Key doesn't exist yet */
  338. ask_to_confirm(keyblob, keybloblen, algoname);
  339. /* If we get here, they said yes */
  340. if (readonly) {
  341. TRACE(("readonly"))
  342. goto out;
  343. }
  344. if (!cli_opts.always_accept_key) {
  345. /* put the new entry in the file */
  346. fseek(hostsfile, 0, SEEK_END); /* In case it wasn't opened append */
  347. buf_setpos(line, 0);
  348. buf_setlen(line, 0);
  349. buf_putbytes(line, (const unsigned char *) cli_opts.remotehost, hostlen);
  350. buf_putbyte(line, ' ');
  351. buf_putbytes(line, (const unsigned char *) algoname, algolen);
  352. buf_putbyte(line, ' ');
  353. len = line->size - line->pos;
  354. /* The only failure with base64 is buffer_overflow, but buf_getwriteptr
  355. * will die horribly in the case anyway */
  356. base64_encode(keyblob, keybloblen, buf_getwriteptr(line, len), &len);
  357. buf_incrwritepos(line, len);
  358. buf_putbyte(line, '\n');
  359. buf_setpos(line, 0);
  360. fwrite(buf_getptr(line, line->len), line->len, 1, hostsfile);
  361. /* We ignore errors, since there's not much we can do about them */
  362. }
  363. out:
  364. if (hostsfile != NULL) {
  365. fclose(hostsfile);
  366. }
  367. if (line != NULL) {
  368. buf_free(line);
  369. }
  370. m_free(fingerprint);
  371. }
  372. void recv_msg_ext_info(void) {
  373. /* This message is not client-specific in the protocol but Dropbear only handles
  374. a server-sent message at present. */
  375. unsigned int num_ext;
  376. unsigned int i;
  377. TRACE(("enter recv_msg_ext_info"))
  378. /* Must be after the first SSH_MSG_NEWKEYS */
  379. TRACE(("last %d, donefirst %d, donescond %d", ses.lastpacket, ses.kexstate.donefirstkex, ses.kexstate.donesecondkex))
  380. if (!(ses.lastpacket == SSH_MSG_NEWKEYS && !ses.kexstate.donesecondkex)) {
  381. TRACE(("leave recv_msg_ext_info: ignoring packet received at the wrong time"))
  382. return;
  383. }
  384. num_ext = buf_getint(ses.payload);
  385. TRACE(("received SSH_MSG_EXT_INFO with %d items", num_ext))
  386. for (i = 0; i < num_ext; i++) {
  387. unsigned int name_len;
  388. char *ext_name = buf_getstring(ses.payload, &name_len);
  389. TRACE(("extension %d name '%s'", i, ext_name))
  390. if (cli_ses.server_sig_algs == NULL
  391. && name_len == strlen(SSH_SERVER_SIG_ALGS)
  392. && strcmp(ext_name, SSH_SERVER_SIG_ALGS) == 0) {
  393. cli_ses.server_sig_algs = buf_getbuf(ses.payload);
  394. } else {
  395. /* valid extension values could be >MAX_STRING_LEN */
  396. buf_eatstring(ses.payload);
  397. }
  398. m_free(ext_name);
  399. }
  400. TRACE(("leave recv_msg_ext_info"))
  401. }