2 * Dropbear - a SSH2 server
4 * Copyright (c) 2002-2004 Matt Johnston
5 * Copyright (c) 2004 by Mihnea Stoenescu
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:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
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
41 static void checkhostkey(unsigned char* keyblob
, unsigned int keybloblen
);
42 #define MAX_KNOWNHOSTS_LINE 4500
44 void send_msg_kexdh_init() {
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
);
50 gen_kexdh_vals(cli_ses
.dh_e
, cli_ses
.dh_x
);
53 buf_putbyte(ses
.writepayload
, SSH_MSG_KEXDH_INIT
);
54 buf_putmpint(ses
.writepayload
, cli_ses
.dh_e
);
56 ses
.requirenext
= SSH_MSG_KEXDH_REPLY
;
59 /* Handle a diffie-hellman key exchange reply. */
60 void recv_msg_kexdh_reply() {
63 sign_key
*hostkey
= NULL
;
64 unsigned int type
, keybloblen
;
65 unsigned char* keyblob
= NULL
;
68 TRACE(("enter recv_msg_kexdh_reply"))
70 if (cli_ses
.kex_state
!= KEXDH_INIT_SENT
) {
71 dropbear_exit("Received out-of-order kexdhreply");
74 type
= ses
.newkeys
->algo_hostkey
;
75 TRACE(("type is %d", type
))
77 hostkey
= new_sign_key();
78 keybloblen
= buf_getint(ses
.payload
);
80 keyblob
= buf_getptr(ses
.payload
, keybloblen
);
81 if (!ses
.kexstate
.donefirstkex
) {
82 /* Only makes sense the first time */
83 checkhostkey(keyblob
, keybloblen
);
86 if (buf_get_pub_key(ses
.payload
, hostkey
, &type
) != DROPBEAR_SUCCESS
) {
87 TRACE(("failed getting pubkey"))
88 dropbear_exit("Bad KEX packet");
91 if (buf_getmpint(ses
.payload
, &dh_f
) != DROPBEAR_SUCCESS
) {
92 TRACE(("failed getting mpint"))
93 dropbear_exit("Bad KEX packet");
96 kexdh_comb_key(cli_ses
.dh_e
, cli_ses
.dh_x
, &dh_f
, hostkey
);
98 mp_clear_multi(cli_ses
.dh_e
, cli_ses
.dh_x
, NULL
);
100 m_free(cli_ses
.dh_x
);
102 if (buf_verify(ses
.payload
, hostkey
, ses
.hash
, SHA1_HASH_SIZE
)
103 != DROPBEAR_SUCCESS
) {
104 dropbear_exit("Bad hostkey signature");
107 sign_key_free(hostkey
);
111 ses
.requirenext
= SSH_MSG_NEWKEYS
;
112 TRACE(("leave recv_msg_kexdh_init"))
115 static void ask_to_confirm(unsigned char* keyblob
, unsigned int keybloblen
) {
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",
129 fprintf(stderr
, "\nHost '%s' is not in the trusted hosts file.\n(fingerprint %s)\nDo you want to continue connecting? (y/n) ",
134 tty
= fopen(_PATH_TTY
, "r");
136 response
= getc(tty
);
139 response
= getc(stdin
);
142 if (response
== 'y') {
146 dropbear_exit("Didn't validate host key");
149 static FILE* open_known_hosts_file(int * readonly
)
151 FILE * hostsfile
= NULL
;
152 char * filename
= NULL
;
153 char * homedir
= NULL
;
155 homedir
= getenv("HOME");
158 struct passwd
* pw
= NULL
;
159 pw
= getpwuid(getuid());
161 homedir
= pw
->pw_dir
;
167 len
= strlen(homedir
);
168 filename
= m_malloc(len
+ 18); /* "/.ssh/known_hosts" and null-terminator*/
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
)))
181 snprintf(filename
, len
+18, "%s/.ssh/known_hosts", homedir
);
182 hostsfile
= fopen(filename
, "a+");
184 if (hostsfile
!= NULL
) {
186 fseek(hostsfile
, 0, SEEK_SET
);
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
)))
192 hostsfile
= fopen(filename
, "r");
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",
209 static void checkhostkey(unsigned char* keyblob
, unsigned int keybloblen
) {
211 FILE *hostsfile
= NULL
;
213 unsigned int hostlen
, algolen
;
215 const char *algoname
= NULL
;
216 char * fingerprint
= NULL
;
217 buffer
* line
= NULL
;
220 hostsfile
= open_known_hosts_file(&readonly
);
222 ask_to_confirm(keyblob
, keybloblen
);
223 /* ask_to_confirm will exit upon failure */
227 line
= buf_new(MAX_KNOWNHOSTS_LINE
);
228 hostlen
= strlen(cli_opts
.remotehost
);
229 algoname
= signkey_name_from_type(ses
.newkeys
->algo_hostkey
, &algolen
);
232 if (buf_getline(line
, hostsfile
) == DROPBEAR_FAILURE
) {
233 TRACE(("failed reading line: prob EOF"))
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"))
246 /* Compare hostnames */
247 if (strncmp(cli_opts
.remotehost
, buf_getptr(line
, hostlen
),
249 TRACE(("hosts don't match"))
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"))
260 if (strncmp(buf_getptr(line
, algolen
), algoname
, algolen
) != 0) {
261 TRACE(("algo doesn't match"))
265 buf_incrpos(line
, algolen
);
266 if (buf_getbyte(line
) != ' ') {
267 TRACE(("missing space after algo"))
271 /* Now we're at the interesting hostkey */
272 ret
= cmp_base64_key(keyblob
, keybloblen
, algoname
, algolen
,
275 if (ret
== DROPBEAR_SUCCESS
) {
276 /* Good matching key */
277 TRACE(("good matching key"))
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"
286 "If you know that the host key is correct you can\nremove the bad entry from ~/.ssh/known_hosts",
288 sign_key_fingerprint(keyblob
, keybloblen
),
289 fingerprint
? fingerprint
: "UNKNOWN");
290 } while (1); /* keep going 'til something happens */
292 /* Key doesn't exist yet */
293 ask_to_confirm(keyblob
, keybloblen
);
295 /* If we get here, they said yes */
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 */
307 buf_putbytes(line
, cli_opts
.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');
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 */
324 if (hostsfile
!= NULL
) {