Mercurial > dropbear
comparison cli-kex.c @ 285:1b9e69c058d2
propagate from branch 'au.asn.ucc.matt.ltc.dropbear' (head 20dccfc09627970a312d77fb41dc2970b62689c3)
to branch 'au.asn.ucc.matt.dropbear' (head fdf4a7a3b97ae5046139915de7e40399cceb2c01)
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Wed, 08 Mar 2006 13:23:58 +0000 |
parents | a62cb364f615 |
children | 740e782679be 9916350d7d8b |
comparison
equal
deleted
inserted
replaced
281:997e6f7dc01e | 285:1b9e69c058d2 |
---|---|
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 fprintf(stderr, "\nHost '%s' is not in the trusted hosts file.\n(fingerprint %s)\nDo you want to continue connecting? (y/n)\n", | |
123 cli_opts.remotehost, | |
124 fp); | |
125 | |
126 tty = fopen(_PATH_TTY, "r"); | |
127 if (tty) { | |
128 response = getc(tty); | |
129 fclose(tty); | |
130 } else { | |
131 response = getc(stdin); | |
132 } | |
133 | |
134 if (response == 'y') { | |
135 m_free(fp); | |
136 return; | |
137 } | |
138 | |
139 dropbear_exit("Didn't validate host key"); | |
140 } | |
141 | |
142 static void checkhostkey(unsigned char* keyblob, unsigned int keybloblen) { | |
143 | |
144 char * filename = NULL; | |
145 FILE *hostsfile = NULL; | |
146 int readonly = 0; | |
147 struct passwd *pw = NULL; | |
148 unsigned int hostlen, algolen; | |
149 unsigned long len; | |
150 const char *algoname = NULL; | |
151 buffer * line = NULL; | |
152 int ret; | |
153 | |
154 pw = getpwuid(getuid()); | |
155 | |
156 if (pw == NULL) { | |
157 dropbear_exit("Failed to get homedir"); | |
158 } | |
159 | |
160 len = strlen(pw->pw_dir); | |
161 filename = m_malloc(len + 18); /* "/.ssh/known_hosts" and null-terminator*/ | |
162 | |
163 snprintf(filename, len+18, "%s/.ssh", pw->pw_dir); | |
164 /* Check that ~/.ssh exists - easiest way is just to mkdir */ | |
165 if (mkdir(filename, S_IRWXU) != 0) { | |
166 if (errno != EEXIST) { | |
167 dropbear_log(LOG_INFO, "Warning: failed creating ~/.ssh: %s", | |
168 strerror(errno)); | |
169 TRACE(("mkdir didn't work: %s", strerror(errno))) | |
170 ask_to_confirm(keyblob, keybloblen); | |
171 goto out; /* only get here on success */ | |
172 } | |
173 } | |
174 | |
175 snprintf(filename, len+18, "%s/.ssh/known_hosts", pw->pw_dir); | |
176 hostsfile = fopen(filename, "a+"); | |
177 | |
178 if (hostsfile != NULL) { | |
179 fseek(hostsfile, 0, SEEK_SET); | |
180 } else { | |
181 /* We mightn't have been able to open it if it was read-only */ | |
182 if (errno == EACCES || errno == EROFS) { | |
183 TRACE(("trying readonly: %s", strerror(errno))) | |
184 readonly = 1; | |
185 hostsfile = fopen(filename, "r"); | |
186 } | |
187 } | |
188 | |
189 if (hostsfile == NULL) { | |
190 TRACE(("hostsfile didn't open: %s", strerror(errno))) | |
191 ask_to_confirm(keyblob, keybloblen); | |
192 goto out; /* We only get here on success */ | |
193 } | |
194 | |
195 line = buf_new(MAX_KNOWNHOSTS_LINE); | |
196 hostlen = strlen(cli_opts.remotehost); | |
197 algoname = signkey_name_from_type(ses.newkeys->algo_hostkey, &algolen); | |
198 | |
199 do { | |
200 if (buf_getline(line, hostsfile) == DROPBEAR_FAILURE) { | |
201 TRACE(("failed reading line: prob EOF")) | |
202 break; | |
203 } | |
204 | |
205 /* The line is too short to be sensible */ | |
206 /* "30" is 'enough to hold ssh-dss plus the spaces, ie so we don't | |
207 * buf_getfoo() past the end and die horribly - the base64 parsing | |
208 * code is what tiptoes up to the end nicely */ | |
209 if (line->len < (hostlen+30) ) { | |
210 TRACE(("line is too short to be sensible")) | |
211 continue; | |
212 } | |
213 | |
214 /* Compare hostnames */ | |
215 if (strncmp(cli_opts.remotehost, buf_getptr(line, hostlen), | |
216 hostlen) != 0) { | |
217 TRACE(("hosts don't match")) | |
218 continue; | |
219 } | |
220 | |
221 buf_incrpos(line, hostlen); | |
222 if (buf_getbyte(line) != ' ') { | |
223 /* there wasn't a space after the hostname, something dodgy */ | |
224 TRACE(("missing space afte matching hostname")) | |
225 continue; | |
226 } | |
227 | |
228 if ( strncmp(buf_getptr(line, algolen), algoname, algolen) != 0) { | |
229 TRACE(("algo doesn't match")) | |
230 continue; | |
231 } | |
232 | |
233 buf_incrpos(line, algolen); | |
234 if (buf_getbyte(line) != ' ') { | |
235 TRACE(("missing space after algo")) | |
236 continue; | |
237 } | |
238 | |
239 /* Now we're at the interesting hostkey */ | |
240 ret = cmp_base64_key(keyblob, keybloblen, algoname, algolen, line); | |
241 | |
242 if (ret == DROPBEAR_SUCCESS) { | |
243 /* Good matching key */ | |
244 TRACE(("good matching key")) | |
245 goto out; | |
246 } | |
247 | |
248 /* The keys didn't match. eep. */ | |
249 } while (1); /* keep going 'til something happens */ | |
250 | |
251 /* Key doesn't exist yet */ | |
252 ask_to_confirm(keyblob, keybloblen); | |
253 | |
254 /* If we get here, they said yes */ | |
255 | |
256 if (readonly) { | |
257 TRACE(("readonly")) | |
258 goto out; | |
259 } | |
260 | |
261 /* put the new entry in the file */ | |
262 fseek(hostsfile, 0, SEEK_END); /* In case it wasn't opened append */ | |
263 buf_setpos(line, 0); | |
264 buf_setlen(line, 0); | |
265 buf_putbytes(line, ses.remotehost, hostlen); | |
266 buf_putbyte(line, ' '); | |
267 buf_putbytes(line, algoname, algolen); | |
268 buf_putbyte(line, ' '); | |
269 len = line->size - line->pos; | |
270 TRACE(("keybloblen %d, len %d", keybloblen, len)) | |
271 /* The only failure with base64 is buffer_overflow, but buf_getwriteptr | |
272 * will die horribly in the case anyway */ | |
273 base64_encode(keyblob, keybloblen, buf_getwriteptr(line, len), &len); | |
274 buf_incrwritepos(line, len); | |
275 buf_putbyte(line, '\n'); | |
276 buf_setpos(line, 0); | |
277 fwrite(buf_getptr(line, line->len), line->len, 1, hostsfile); | |
278 /* We ignore errors, since there's not much we can do about them */ | |
279 | |
280 out: | |
281 if (hostsfile != NULL) { | |
282 fclose(hostsfile); | |
283 } | |
284 m_free(filename); | |
285 if (line != NULL) { | |
286 buf_free(line); | |
287 } | |
288 } |