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
42 static void checkhostkey(unsigned char* keyblob
, unsigned int keybloblen
);
43 #define MAX_KNOWNHOSTS_LINE 4500
45 void send_msg_kexdh_init() {
46 TRACE(("send_msg_kexdh_init()"))
49 buf_putbyte(ses
.writepayload
, SSH_MSG_KEXDH_INIT
);
50 switch (ses
.newkeys
->algo_kex
->mode
) {
51 case DROPBEAR_KEX_NORMAL_DH
:
52 if (ses
.newkeys
->algo_kex
!= cli_ses
.param_kex_algo
53 || !cli_ses
.dh_param
) {
54 if (cli_ses
.dh_param
) {
55 free_kexdh_param(cli_ses
.dh_param
);
57 cli_ses
.dh_param
= gen_kexdh_param();
59 buf_putmpint(ses
.writepayload
, &cli_ses
.dh_param
->pub
);
61 case DROPBEAR_KEX_ECDH
:
63 if (ses
.newkeys
->algo_kex
!= cli_ses
.param_kex_algo
64 || !cli_ses
.ecdh_param
) {
65 if (cli_ses
.ecdh_param
) {
66 free_kexecdh_param(cli_ses
.ecdh_param
);
68 cli_ses
.ecdh_param
= gen_kexecdh_param();
70 buf_put_ecc_raw_pubkey_string(ses
.writepayload
, &cli_ses
.ecdh_param
->key
);
73 #ifdef DROPBEAR_CURVE25519
74 case DROPBEAR_KEX_CURVE25519
:
75 if (ses
.newkeys
->algo_kex
!= cli_ses
.param_kex_algo
76 || !cli_ses
.curve25519_param
) {
77 if (cli_ses
.curve25519_param
) {
78 free_kexcurve25519_param(cli_ses
.curve25519_param
);
80 cli_ses
.curve25519_param
= gen_kexcurve25519_param();
82 buf_putstring(ses
.writepayload
, (const char*)cli_ses
.curve25519_param
->pub
, CURVE25519_LEN
);
87 cli_ses
.param_kex_algo
= ses
.newkeys
->algo_kex
;
91 /* Handle a diffie-hellman key exchange reply. */
92 void recv_msg_kexdh_reply() {
94 sign_key
*hostkey
= NULL
;
95 unsigned int type
, keybloblen
;
96 unsigned char* keyblob
= NULL
;
98 TRACE(("enter recv_msg_kexdh_reply"))
100 if (cli_ses
.kex_state
!= KEXDH_INIT_SENT
) {
101 dropbear_exit("Received out-of-order kexdhreply");
103 type
= ses
.newkeys
->algo_hostkey
;
104 TRACE(("type is %d", type
))
106 hostkey
= new_sign_key();
107 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
);
115 if (buf_get_pub_key(ses
.payload
, hostkey
, &type
) != DROPBEAR_SUCCESS
) {
116 TRACE(("failed getting pubkey"))
117 dropbear_exit("Bad KEX packet");
120 switch (ses
.newkeys
->algo_kex
->mode
) {
121 case DROPBEAR_KEX_NORMAL_DH
:
125 if (buf_getmpint(ses
.payload
, &dh_f
) != DROPBEAR_SUCCESS
) {
126 TRACE(("failed getting mpint"))
127 dropbear_exit("Bad KEX packet");
130 kexdh_comb_key(cli_ses
.dh_param
, &dh_f
, hostkey
);
134 case DROPBEAR_KEX_ECDH
:
137 buffer
*ecdh_qs
= buf_getstringbuf(ses
.payload
);
138 kexecdh_comb_key(cli_ses
.ecdh_param
, ecdh_qs
, hostkey
);
143 #ifdef DROPBEAR_CURVE25519
144 case DROPBEAR_KEX_CURVE25519
:
146 buffer
*ecdh_qs
= buf_getstringbuf(ses
.payload
);
147 kexcurve25519_comb_key(cli_ses
.curve25519_param
, ecdh_qs
, hostkey
);
154 if (cli_ses
.dh_param
) {
155 free_kexdh_param(cli_ses
.dh_param
);
156 cli_ses
.dh_param
= NULL
;
159 if (cli_ses
.ecdh_param
) {
160 free_kexecdh_param(cli_ses
.ecdh_param
);
161 cli_ses
.ecdh_param
= NULL
;
164 #ifdef DROPBEAR_CURVE25519
165 if (cli_ses
.curve25519_param
) {
166 free_kexcurve25519_param(cli_ses
.curve25519_param
);
167 cli_ses
.curve25519_param
= NULL
;
171 cli_ses
.param_kex_algo
= NULL
;
172 if (buf_verify(ses
.payload
, hostkey
, ses
.hash
) != DROPBEAR_SUCCESS
) {
173 dropbear_exit("Bad hostkey signature");
176 sign_key_free(hostkey
);
180 ses
.requirenext
= SSH_MSG_NEWKEYS
;
181 TRACE(("leave recv_msg_kexdh_init"))
184 static void ask_to_confirm(unsigned char* keyblob
, unsigned int keybloblen
,
185 const char* algoname
) {
191 fp
= sign_key_fingerprint(keyblob
, keybloblen
);
192 if (cli_opts
.always_accept_key
) {
193 fprintf(stderr
, "\nHost '%s' key accepted unconditionally.\n(%s fingerprint %s)\n",
200 fprintf(stderr
, "\nHost '%s' is not in the trusted hosts file.\n(%s fingerprint %s)\nDo you want to continue connecting? (y/n) ",
206 tty
= fopen(_PATH_TTY
, "r");
208 response
= getc(tty
);
211 response
= getc(stdin
);
214 if (response
== 'y') {
218 dropbear_exit("Didn't validate host key");
221 static FILE* open_known_hosts_file(int * readonly
)
223 FILE * hostsfile
= NULL
;
224 char * filename
= NULL
;
225 char * homedir
= NULL
;
227 homedir
= getenv("HOME");
230 struct passwd
* pw
= NULL
;
231 pw
= getpwuid(getuid());
233 homedir
= pw
->pw_dir
;
239 len
= strlen(homedir
);
240 filename
= m_malloc(len
+ 18); /* "/.ssh/known_hosts" and null-terminator*/
242 snprintf(filename
, len
+18, "%s/.ssh", homedir
);
243 /* Check that ~/.ssh exists - easiest way is just to mkdir */
244 if (mkdir(filename
, S_IRWXU
) != 0) {
245 if (errno
!= EEXIST
) {
246 dropbear_log(LOG_INFO
, "Warning: failed creating %s/.ssh: %s",
247 homedir
, strerror(errno
));
248 TRACE(("mkdir didn't work: %s", strerror(errno
)))
253 snprintf(filename
, len
+18, "%s/.ssh/known_hosts", homedir
);
254 hostsfile
= fopen(filename
, "a+");
256 if (hostsfile
!= NULL
) {
258 fseek(hostsfile
, 0, SEEK_SET
);
260 /* We mightn't have been able to open it if it was read-only */
261 if (errno
== EACCES
|| errno
== EROFS
) {
262 TRACE(("trying readonly: %s", strerror(errno
)))
264 hostsfile
= fopen(filename
, "r");
269 if (hostsfile
== NULL
) {
270 TRACE(("hostsfile didn't open: %s", strerror(errno
)))
271 dropbear_log(LOG_WARNING
, "Failed to open %s/.ssh/known_hosts",
281 static void checkhostkey(unsigned char* keyblob
, unsigned int keybloblen
) {
283 FILE *hostsfile
= NULL
;
285 unsigned int hostlen
, algolen
;
287 const char *algoname
= NULL
;
288 char * fingerprint
= NULL
;
289 buffer
* line
= NULL
;
292 if (cli_opts
.no_hostkey_check
) {
293 fprintf(stderr
, "Caution, skipping hostkey check for %s\n", cli_opts
.remotehost
);
297 algoname
= signkey_name_from_type(ses
.newkeys
->algo_hostkey
, &algolen
);
299 hostsfile
= open_known_hosts_file(&readonly
);
301 ask_to_confirm(keyblob
, keybloblen
, algoname
);
302 /* ask_to_confirm will exit upon failure */
306 line
= buf_new(MAX_KNOWNHOSTS_LINE
);
307 hostlen
= strlen(cli_opts
.remotehost
);
310 if (buf_getline(line
, hostsfile
) == DROPBEAR_FAILURE
) {
311 TRACE(("failed reading line: prob EOF"))
315 /* The line is too short to be sensible */
316 /* "30" is 'enough to hold ssh-dss plus the spaces, ie so we don't
317 * buf_getfoo() past the end and die horribly - the base64 parsing
318 * code is what tiptoes up to the end nicely */
319 if (line
->len
< (hostlen
+30) ) {
320 TRACE(("line is too short to be sensible"))
324 /* Compare hostnames */
325 if (strncmp(cli_opts
.remotehost
, (const char *) buf_getptr(line
, hostlen
),
330 buf_incrpos(line
, hostlen
);
331 if (buf_getbyte(line
) != ' ') {
332 /* there wasn't a space after the hostname, something dodgy */
333 TRACE(("missing space afte matching hostname"))
337 if (strncmp((const char *) buf_getptr(line
, algolen
), algoname
, algolen
) != 0) {
338 TRACE(("algo doesn't match"))
342 buf_incrpos(line
, algolen
);
343 if (buf_getbyte(line
) != ' ') {
344 TRACE(("missing space after algo"))
348 /* Now we're at the interesting hostkey */
349 ret
= cmp_base64_key(keyblob
, keybloblen
, (const unsigned char *) algoname
, algolen
,
352 if (ret
== DROPBEAR_SUCCESS
) {
353 /* Good matching key */
354 TRACE(("good matching key"))
358 /* The keys didn't match. eep. Note that we're "leaking"
359 the fingerprint strings here, but we're exiting anyway */
360 dropbear_exit("\n\n%s host key mismatch for %s !\n"
361 "Fingerprint is %s\n"
363 "If you know that the host key is correct you can\nremove the bad entry from ~/.ssh/known_hosts",
366 sign_key_fingerprint(keyblob
, keybloblen
),
367 fingerprint
? fingerprint
: "UNKNOWN");
368 } while (1); /* keep going 'til something happens */
370 /* Key doesn't exist yet */
371 ask_to_confirm(keyblob
, keybloblen
, algoname
);
373 /* If we get here, they said yes */
380 if (!cli_opts
.always_accept_key
) {
381 /* put the new entry in the file */
382 fseek(hostsfile
, 0, SEEK_END
); /* In case it wasn't opened append */
385 buf_putbytes(line
, (const unsigned char *) cli_opts
.remotehost
, hostlen
);
386 buf_putbyte(line
, ' ');
387 buf_putbytes(line
, (const unsigned char *) algoname
, algolen
);
388 buf_putbyte(line
, ' ');
389 len
= line
->size
- line
->pos
;
390 /* The only failure with base64 is buffer_overflow, but buf_getwriteptr
391 * will die horribly in the case anyway */
392 base64_encode(keyblob
, keybloblen
, buf_getwriteptr(line
, len
), &len
);
393 buf_incrwritepos(line
, len
);
394 buf_putbyte(line
, '\n');
396 fwrite(buf_getptr(line
, line
->len
), line
->len
, 1, hostsfile
);
397 /* We ignore errors, since there's not much we can do about them */
401 if (hostsfile
!= NULL
) {