1 /*
2 * known_hosts: Host and public key verification.
3 *
4 * This file is part of the SSH Library
5 *
6 * Copyright (c) 2003-2009 by Aris Adamantiadis
7 * Copyright (c) 2009-2017 by Andreas Schneider <asn@cryptomilk.org>
8 *
9 * The SSH Library is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or (at your
12 * option) any later version.
13 *
14 * The SSH Library is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17 * License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with the SSH Library; see the file COPYING. If not, write to
21 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
22 * MA 02111-1307, USA.
23 */
24
25 #include "config.h"
26
27 #include <ctype.h>
28 #include <errno.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31
32 #ifndef _WIN32
33 #include <arpa/inet.h>
34 #include <netinet/in.h>
35 #endif
36
37 #include "libssh/priv.h"
38 #include "libssh/dh.h"
39 #include "libssh/session.h"
40 #include "libssh/options.h"
41 #include "libssh/misc.h"
42 #include "libssh/pki.h"
43 #include "libssh/dh.h"
44 #include "libssh/knownhosts.h"
45 #include "libssh/token.h"
46
47 /**
48 * @addtogroup libssh_session
49 *
50 * @{
51 */
52
hash_hostname(const char * name,unsigned char * salt,unsigned int salt_size,unsigned char ** hash,unsigned int * hash_size)53 static int hash_hostname(const char *name,
54 unsigned char *salt,
55 unsigned int salt_size,
56 unsigned char **hash,
57 unsigned int *hash_size)
58 {
59 HMACCTX mac_ctx;
60
61 mac_ctx = hmac_init(salt, salt_size, SSH_HMAC_SHA1);
62 if (mac_ctx == NULL) {
63 return SSH_ERROR;
64 }
65
66 hmac_update(mac_ctx, name, strlen(name));
67 hmac_final(mac_ctx, *hash, hash_size);
68
69 return SSH_OK;
70 }
71
match_hashed_hostname(const char * host,const char * hashed_host)72 static int match_hashed_hostname(const char *host, const char *hashed_host)
73 {
74 char *hashed;
75 char *b64_hash;
76 ssh_buffer salt = NULL;
77 ssh_buffer hash = NULL;
78 unsigned char hashed_buf[256] = {0};
79 unsigned char *hashed_buf_ptr = hashed_buf;
80 unsigned int hashed_buf_size = sizeof(hashed_buf);
81 int cmp;
82 int rc;
83 int match = 0;
84
85 cmp = strncmp(hashed_host, "|1|", 3);
86 if (cmp != 0) {
87 return 0;
88 }
89
90 hashed = strdup(hashed_host + 3);
91 if (hashed == NULL) {
92 return 0;
93 }
94
95 b64_hash = strchr(hashed, '|');
96 if (b64_hash == NULL) {
97 goto error;
98 }
99 *b64_hash = '\0';
100 b64_hash++;
101
102 salt = base64_to_bin(hashed);
103 if (salt == NULL) {
104 goto error;
105 }
106
107 hash = base64_to_bin(b64_hash);
108 if (hash == NULL) {
109 goto error;
110 }
111
112 rc = hash_hostname(host,
113 ssh_buffer_get(salt),
114 ssh_buffer_get_len(salt),
115 &hashed_buf_ptr,
116 &hashed_buf_size);
117 if (rc != SSH_OK) {
118 goto error;
119 }
120
121 if (hashed_buf_size != ssh_buffer_get_len(hash)) {
122 goto error;
123 }
124
125 cmp = memcmp(hashed_buf, ssh_buffer_get(hash), hashed_buf_size);
126 if (cmp == 0) {
127 match = 1;
128 }
129
130 error:
131 free(hashed);
132 SSH_BUFFER_FREE(salt);
133 SSH_BUFFER_FREE(hash);
134
135 return match;
136 }
137
138 /**
139 * @brief Free an allocated ssh_knownhosts_entry.
140 *
141 * Use SSH_KNOWNHOSTS_ENTRY_FREE() to set the pointer to NULL.
142 *
143 * @param[in] entry The entry to free.
144 */
ssh_knownhosts_entry_free(struct ssh_knownhosts_entry * entry)145 void ssh_knownhosts_entry_free(struct ssh_knownhosts_entry *entry)
146 {
147 if (entry == NULL) {
148 return;
149 }
150
151 SAFE_FREE(entry->hostname);
152 SAFE_FREE(entry->unparsed);
153 ssh_key_free(entry->publickey);
154 SAFE_FREE(entry->comment);
155 SAFE_FREE(entry);
156 }
157
known_hosts_read_line(FILE * fp,char * buf,size_t buf_size,size_t * buf_len,size_t * lineno)158 static int known_hosts_read_line(FILE *fp,
159 char *buf,
160 size_t buf_size,
161 size_t *buf_len,
162 size_t *lineno)
163 {
164 while (fgets(buf, buf_size, fp) != NULL) {
165 size_t len;
166 if (buf[0] == '\0') {
167 continue;
168 }
169
170 *lineno += 1;
171 len = strlen(buf);
172 if (buf_len != NULL) {
173 *buf_len = len;
174 }
175 if (buf[len - 1] == '\n' || feof(fp)) {
176 return 0;
177 } else {
178 errno = E2BIG;
179 return -1;
180 }
181 }
182
183 return -1;
184 }
185
186 static int
ssh_known_hosts_entries_compare(struct ssh_knownhosts_entry * k1,struct ssh_knownhosts_entry * k2)187 ssh_known_hosts_entries_compare(struct ssh_knownhosts_entry *k1,
188 struct ssh_knownhosts_entry *k2)
189 {
190 int cmp;
191
192 if (k1 == NULL || k2 == NULL) {
193 return 1;
194 }
195
196 cmp = strcmp(k1->hostname, k2->hostname);
197 if (cmp != 0) {
198 return cmp;
199 }
200
201 cmp = ssh_key_cmp(k1->publickey, k2->publickey, SSH_KEY_CMP_PUBLIC);
202 if (cmp != 0) {
203 return cmp;
204 }
205
206 return 0;
207 }
208
209 /* This method reads the known_hosts file referenced by the path
210 * in filename argument, and entries matching the match argument
211 * will be added to the list in entries argument.
212 * If the entries list is NULL, it will allocate a new list. Caller
213 * is responsible to free it even if an error occurs.
214 */
ssh_known_hosts_read_entries(const char * match,const char * filename,struct ssh_list ** entries)215 static int ssh_known_hosts_read_entries(const char *match,
216 const char *filename,
217 struct ssh_list **entries)
218 {
219 char line[8192];
220 size_t lineno = 0;
221 size_t len = 0;
222 FILE *fp;
223 int rc;
224
225 fp = fopen(filename, "r");
226 if (fp == NULL) {
227 SSH_LOG(SSH_LOG_WARN, "Failed to open the known_hosts file '%s': %s",
228 filename, strerror(errno));
229 /* The missing file is not an error here */
230 return SSH_OK;
231 }
232
233 if (*entries == NULL) {
234 *entries = ssh_list_new();
235 if (*entries == NULL) {
236 fclose(fp);
237 return SSH_ERROR;
238 }
239 }
240
241 for (rc = known_hosts_read_line(fp, line, sizeof(line), &len, &lineno);
242 rc == 0;
243 rc = known_hosts_read_line(fp, line, sizeof(line), &len, &lineno)) {
244 struct ssh_knownhosts_entry *entry = NULL;
245 struct ssh_iterator *it = NULL;
246 char *p = NULL;
247
248 if (line[len] != '\n') {
249 len = strcspn(line, "\n");
250 }
251 line[len] = '\0';
252
253 /* Skip leading spaces */
254 for (p = line; isspace((int)p[0]); p++);
255
256 /* Skip comments and empty lines */
257 if (p[0] == '\0' || p[0] == '#') {
258 continue;
259 }
260
261 /* Skip lines starting with markers (@cert-authority, @revoked):
262 * we do not completely support them anyway */
263 if (p[0] == '@') {
264 continue;
265 }
266
267 rc = ssh_known_hosts_parse_line(match,
268 line,
269 &entry);
270 if (rc == SSH_AGAIN) {
271 continue;
272 } else if (rc != SSH_OK) {
273 goto error;
274 }
275
276 /* Check for duplicates */
277 for (it = ssh_list_get_iterator(*entries);
278 it != NULL;
279 it = it->next) {
280 struct ssh_knownhosts_entry *entry2;
281 int cmp;
282 entry2 = ssh_iterator_value(struct ssh_knownhosts_entry *, it);
283 cmp = ssh_known_hosts_entries_compare(entry, entry2);
284 if (cmp == 0) {
285 ssh_knownhosts_entry_free(entry);
286 entry = NULL;
287 break;
288 }
289 }
290 if (entry != NULL) {
291 ssh_list_append(*entries, entry);
292 }
293 }
294
295 fclose(fp);
296 return SSH_OK;
297 error:
298 fclose(fp);
299 return SSH_ERROR;
300 }
301
ssh_session_get_host_port(ssh_session session)302 static char *ssh_session_get_host_port(ssh_session session)
303 {
304 char *host_port;
305 char *host;
306
307 if (session->opts.host == NULL) {
308 ssh_set_error(session,
309 SSH_FATAL,
310 "Can't verify server in known hosts if the host we "
311 "should connect to has not been set");
312
313 return NULL;
314 }
315
316 host = ssh_lowercase(session->opts.host);
317 if (host == NULL) {
318 ssh_set_error_oom(session);
319 return NULL;
320 }
321
322 if (session->opts.port == 0 || session->opts.port == 22) {
323 host_port = host;
324 } else {
325 host_port = ssh_hostport(host, session->opts.port);
326 SAFE_FREE(host);
327 if (host_port == NULL) {
328 ssh_set_error_oom(session);
329 return NULL;
330 }
331 }
332
333 return host_port;
334 }
335
336 /**
337 * @internal
338 * @brief Check which host keys should be preferred for the session.
339 *
340 * This checks the known_hosts file to find out which algorithms should be
341 * preferred for the connection we are going to establish.
342 *
343 * @param[in] session The ssh session to use.
344 *
345 * @return A list of supported key types, NULL on error.
346 */
ssh_known_hosts_get_algorithms(ssh_session session)347 struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session)
348 {
349 struct ssh_list *entry_list = NULL;
350 struct ssh_iterator *it = NULL;
351 char *host_port = NULL;
352 size_t count;
353 struct ssh_list *list = NULL;
354 int list_error = 0;
355 int rc;
356
357 if (session->opts.knownhosts == NULL ||
358 session->opts.global_knownhosts == NULL) {
359 if (ssh_options_apply(session) < 0) {
360 ssh_set_error(session,
361 SSH_REQUEST_DENIED,
362 "Can't find a known_hosts file");
363
364 return NULL;
365 }
366 }
367
368 host_port = ssh_session_get_host_port(session);
369 if (host_port == NULL) {
370 return NULL;
371 }
372
373 list = ssh_list_new();
374 if (list == NULL) {
375 ssh_set_error_oom(session);
376 SAFE_FREE(host_port);
377 return NULL;
378 }
379
380 rc = ssh_known_hosts_read_entries(host_port,
381 session->opts.knownhosts,
382 &entry_list);
383 if (rc != 0) {
384 ssh_list_free(entry_list);
385 ssh_list_free(list);
386 return NULL;
387 }
388
389 rc = ssh_known_hosts_read_entries(host_port,
390 session->opts.global_knownhosts,
391 &entry_list);
392 SAFE_FREE(host_port);
393 if (rc != 0) {
394 ssh_list_free(entry_list);
395 ssh_list_free(list);
396 return NULL;
397 }
398
399 if (entry_list == NULL) {
400 ssh_list_free(list);
401 return NULL;
402 }
403
404 count = ssh_list_count(entry_list);
405 if (count == 0) {
406 ssh_list_free(list);
407 ssh_list_free(entry_list);
408 return NULL;
409 }
410
411 for (it = ssh_list_get_iterator(entry_list);
412 it != NULL;
413 it = ssh_list_get_iterator(entry_list)) {
414 struct ssh_iterator *it2 = NULL;
415 struct ssh_knownhosts_entry *entry = NULL;
416 const char *algo = NULL;
417 bool present = false;
418
419 entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it);
420 algo = entry->publickey->type_c;
421
422 /* Check for duplicates */
423 for (it2 = ssh_list_get_iterator(list);
424 it2 != NULL;
425 it2 = it2->next) {
426 char *alg2 = ssh_iterator_value(char *, it2);
427 int cmp = strcmp(alg2, algo);
428 if (cmp == 0) {
429 present = true;
430 break;
431 }
432 }
433
434 /* Add to the new list only if it is unique */
435 if (!present) {
436 rc = ssh_list_append(list, algo);
437 if (rc != SSH_OK) {
438 list_error = 1;
439 }
440 }
441
442 ssh_knownhosts_entry_free(entry);
443 ssh_list_remove(entry_list, it);
444 }
445 ssh_list_free(entry_list);
446 if (list_error) {
447 goto error;
448 }
449
450 return list;
451 error:
452 ssh_list_free(list);
453 return NULL;
454 }
455
456 /**
457 * @internal
458 *
459 * @brief Returns a static string containing a list of the signature types the
460 * given key type can generate.
461 *
462 * @returns A static cstring containing the signature types the key is able to
463 * generate separated by commas; NULL in case of error
464 */
ssh_known_host_sigs_from_hostkey_type(enum ssh_keytypes_e type)465 static const char *ssh_known_host_sigs_from_hostkey_type(enum ssh_keytypes_e type)
466 {
467 switch (type) {
468 case SSH_KEYTYPE_RSA:
469 return "rsa-sha2-512,rsa-sha2-256,ssh-rsa";
470 case SSH_KEYTYPE_ED25519:
471 return "ssh-ed25519";
472 #ifdef HAVE_DSA
473 case SSH_KEYTYPE_DSS:
474 return "ssh-dss";
475 #endif
476 #ifdef HAVE_ECDH
477 case SSH_KEYTYPE_ECDSA_P256:
478 return "ecdsa-sha2-nistp256";
479 case SSH_KEYTYPE_ECDSA_P384:
480 return "ecdsa-sha2-nistp384";
481 case SSH_KEYTYPE_ECDSA_P521:
482 return "ecdsa-sha2-nistp521";
483 #endif
484 case SSH_KEYTYPE_UNKNOWN:
485 default:
486 SSH_LOG(SSH_LOG_WARN, "The given type %d is not a base private key type "
487 "or is unsupported", type);
488 return NULL;
489 }
490 }
491
492 /**
493 * @internal
494 * @brief Get the host keys algorithms identifiers from the known_hosts files
495 *
496 * This expands the signatures types that can be generated from the keys types
497 * present in the known_hosts files
498 *
499 * @param[in] session The ssh session to use.
500 *
501 * @return A newly allocated cstring containing a list of signature algorithms
502 * that can be generated by the host using the keys listed in the known_hosts
503 * files, NULL on error.
504 */
ssh_known_hosts_get_algorithms_names(ssh_session session)505 char *ssh_known_hosts_get_algorithms_names(ssh_session session)
506 {
507 char methods_buffer[256 + 1] = {0};
508 struct ssh_list *entry_list = NULL;
509 struct ssh_iterator *it = NULL;
510 char *host_port = NULL;
511 size_t count;
512 bool needcomma = false;
513 char *names;
514
515 int rc;
516
517 if (session->opts.knownhosts == NULL ||
518 session->opts.global_knownhosts == NULL) {
519 if (ssh_options_apply(session) < 0) {
520 ssh_set_error(session,
521 SSH_REQUEST_DENIED,
522 "Can't find a known_hosts file");
523
524 return NULL;
525 }
526 }
527
528 host_port = ssh_session_get_host_port(session);
529 if (host_port == NULL) {
530 return NULL;
531 }
532
533 rc = ssh_known_hosts_read_entries(host_port,
534 session->opts.knownhosts,
535 &entry_list);
536 if (rc != 0) {
537 SAFE_FREE(host_port);
538 ssh_list_free(entry_list);
539 return NULL;
540 }
541
542 rc = ssh_known_hosts_read_entries(host_port,
543 session->opts.global_knownhosts,
544 &entry_list);
545 SAFE_FREE(host_port);
546 if (rc != 0) {
547 ssh_list_free(entry_list);
548 return NULL;
549 }
550
551 if (entry_list == NULL) {
552 return NULL;
553 }
554
555 count = ssh_list_count(entry_list);
556 if (count == 0) {
557 ssh_list_free(entry_list);
558 return NULL;
559 }
560
561 for (it = ssh_list_get_iterator(entry_list);
562 it != NULL;
563 it = ssh_list_get_iterator(entry_list))
564 {
565 struct ssh_knownhosts_entry *entry = NULL;
566 const char *algo = NULL;
567
568 entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it);
569 algo = ssh_known_host_sigs_from_hostkey_type(entry->publickey->type);
570 if (algo == NULL) {
571 continue;
572 }
573
574 if (needcomma) {
575 strncat(methods_buffer,
576 ",",
577 sizeof(methods_buffer) - strlen(methods_buffer) - 1);
578 }
579
580 strncat(methods_buffer,
581 algo,
582 sizeof(methods_buffer) - strlen(methods_buffer) - 1);
583 needcomma = true;
584
585 ssh_knownhosts_entry_free(entry);
586 ssh_list_remove(entry_list, it);
587 }
588
589 ssh_list_free(entry_list);
590
591 names = ssh_remove_duplicates(methods_buffer);
592
593 return names;
594 }
595
596 /**
597 * @brief Parse a line from a known_hosts entry into a structure
598 *
599 * This parses an known_hosts entry into a structure with the key in a libssh
600 * consumeable form. You can use the PKI key function to further work with it.
601 *
602 * @param[in] hostname The hostname to match the line to
603 *
604 * @param[in] line The line to compare and parse if we have a hostname
605 * match.
606 *
607 * @param[in] entry A pointer to store the the allocated known_hosts
608 * entry structure. The user needs to free the memory
609 * using SSH_KNOWNHOSTS_ENTRY_FREE().
610 *
611 * @return SSH_OK on success, SSH_ERROR otherwise.
612 */
ssh_known_hosts_parse_line(const char * hostname,const char * line,struct ssh_knownhosts_entry ** entry)613 int ssh_known_hosts_parse_line(const char *hostname,
614 const char *line,
615 struct ssh_knownhosts_entry **entry)
616 {
617 struct ssh_knownhosts_entry *e = NULL;
618 char *known_host = NULL;
619 char *p;
620 enum ssh_keytypes_e key_type;
621 int match = 0;
622 int rc = SSH_OK;
623
624 known_host = strdup(line);
625 if (known_host == NULL) {
626 return SSH_ERROR;
627 }
628
629 /* match pattern for hostname or hashed hostname */
630 p = strtok(known_host, " ");
631 if (p == NULL ) {
632 free(known_host);
633 return SSH_ERROR;
634 }
635
636 e = calloc(1, sizeof(struct ssh_knownhosts_entry));
637 if (e == NULL) {
638 free(known_host);
639 return SSH_ERROR;
640 }
641
642 if (hostname != NULL) {
643 char *host_port = NULL;
644 char *q = NULL;
645
646 /* Hashed */
647 if (p[0] == '|') {
648 match = match_hashed_hostname(hostname, p);
649 }
650
651 for (q = strtok(p, ",");
652 q != NULL;
653 q = strtok(NULL, ",")) {
654 int cmp;
655
656 if (q[0] == '[' && hostname[0] != '[') {
657 /* Corner case: We have standard port so we do not have
658 * hostname in square braces. But the patern is enclosed
659 * in braces with, possibly standard or wildcard, port.
660 * We need to test against [host]:port pair here.
661 */
662 if (host_port == NULL) {
663 host_port = ssh_hostport(hostname, 22);
664 if (host_port == NULL) {
665 rc = SSH_ERROR;
666 goto out;
667 }
668 }
669
670 cmp = match_hostname(host_port, q, strlen(q));
671 } else {
672 cmp = match_hostname(hostname, q, strlen(q));
673 }
674 if (cmp == 1) {
675 match = 1;
676 break;
677 }
678 }
679 free(host_port);
680
681 if (match == 0) {
682 rc = SSH_AGAIN;
683 goto out;
684 }
685
686 e->hostname = strdup(hostname);
687 if (e->hostname == NULL) {
688 rc = SSH_ERROR;
689 goto out;
690 }
691 }
692
693 /* Restart parsing */
694 SAFE_FREE(known_host);
695 known_host = strdup(line);
696 if (known_host == NULL) {
697 rc = SSH_ERROR;
698 goto out;
699 }
700
701 p = strtok(known_host, " ");
702 if (p == NULL ) {
703 rc = SSH_ERROR;
704 goto out;
705 }
706
707 e->unparsed = strdup(p);
708 if (e->unparsed == NULL) {
709 rc = SSH_ERROR;
710 goto out;
711 }
712
713 /* pubkey type */
714 p = strtok(NULL, " ");
715 if (p == NULL) {
716 rc = SSH_ERROR;
717 goto out;
718 }
719
720 key_type = ssh_key_type_from_name(p);
721 if (key_type == SSH_KEYTYPE_UNKNOWN) {
722 SSH_LOG(SSH_LOG_WARN, "key type '%s' unknown!", p);
723 rc = SSH_ERROR;
724 goto out;
725 }
726
727 /* public key */
728 p = strtok(NULL, " ");
729 if (p == NULL) {
730 rc = SSH_ERROR;
731 goto out;
732 }
733
734 rc = ssh_pki_import_pubkey_base64(p,
735 key_type,
736 &e->publickey);
737 if (rc != SSH_OK) {
738 SSH_LOG(SSH_LOG_WARN,
739 "Failed to parse %s key for entry: %s!",
740 ssh_key_type_to_char(key_type),
741 e->unparsed);
742 goto out;
743 }
744
745 /* comment */
746 p = strtok(NULL, " ");
747 if (p != NULL) {
748 p = strstr(line, p);
749 if (p != NULL) {
750 e->comment = strdup(p);
751 if (e->comment == NULL) {
752 rc = SSH_ERROR;
753 goto out;
754 }
755 }
756 }
757
758 *entry = e;
759 SAFE_FREE(known_host);
760
761 return SSH_OK;
762 out:
763 SAFE_FREE(known_host);
764 ssh_knownhosts_entry_free(e);
765 return rc;
766 }
767
768 /**
769 * @brief Check if the set hostname and port matches an entry in known_hosts.
770 *
771 * This check if the set hostname and port has an entry in the known_hosts file.
772 * You need to set at least the hostname using ssh_options_set().
773 *
774 * @param[in] session The session with with the values set to check.
775 *
776 * @return A ssh_known_hosts_e return value.
777 */
ssh_session_has_known_hosts_entry(ssh_session session)778 enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session)
779 {
780 struct ssh_list *entry_list = NULL;
781 struct ssh_iterator *it = NULL;
782 char *host_port = NULL;
783 bool global_known_hosts_found = false;
784 bool known_hosts_found = false;
785 int rc;
786
787 if (session->opts.knownhosts == NULL) {
788 if (ssh_options_apply(session) < 0) {
789 ssh_set_error(session,
790 SSH_REQUEST_DENIED,
791 "Cannot find a known_hosts file");
792
793 return SSH_KNOWN_HOSTS_NOT_FOUND;
794 }
795 }
796
797 if (session->opts.knownhosts == NULL &&
798 session->opts.global_knownhosts == NULL) {
799 ssh_set_error(session,
800 SSH_REQUEST_DENIED,
801 "No path set for a known_hosts file");
802
803 return SSH_KNOWN_HOSTS_NOT_FOUND;
804 }
805
806 if (session->opts.knownhosts != NULL) {
807 known_hosts_found = ssh_file_readaccess_ok(session->opts.knownhosts);
808 if (!known_hosts_found) {
809 SSH_LOG(SSH_LOG_WARN, "Cannot access file %s",
810 session->opts.knownhosts);
811 }
812 }
813
814 if (session->opts.global_knownhosts != NULL) {
815 global_known_hosts_found =
816 ssh_file_readaccess_ok(session->opts.global_knownhosts);
817 if (!global_known_hosts_found) {
818 SSH_LOG(SSH_LOG_WARN, "Cannot access file %s",
819 session->opts.global_knownhosts);
820 }
821 }
822
823 if ((!known_hosts_found) && (!global_known_hosts_found)) {
824 ssh_set_error(session,
825 SSH_REQUEST_DENIED,
826 "Cannot find a known_hosts file");
827
828 return SSH_KNOWN_HOSTS_NOT_FOUND;
829 }
830
831 host_port = ssh_session_get_host_port(session);
832 if (host_port == NULL) {
833 return SSH_KNOWN_HOSTS_ERROR;
834 }
835
836 if (known_hosts_found) {
837 rc = ssh_known_hosts_read_entries(host_port,
838 session->opts.knownhosts,
839 &entry_list);
840 if (rc != 0) {
841 SAFE_FREE(host_port);
842 ssh_list_free(entry_list);
843 return SSH_KNOWN_HOSTS_ERROR;
844 }
845 }
846
847 if (global_known_hosts_found) {
848 rc = ssh_known_hosts_read_entries(host_port,
849 session->opts.global_knownhosts,
850 &entry_list);
851 if (rc != 0) {
852 SAFE_FREE(host_port);
853 ssh_list_free(entry_list);
854 return SSH_KNOWN_HOSTS_ERROR;
855 }
856 }
857
858 SAFE_FREE(host_port);
859
860 if (ssh_list_count(entry_list) == 0) {
861 ssh_list_free(entry_list);
862 return SSH_KNOWN_HOSTS_UNKNOWN;
863 }
864
865 for (it = ssh_list_get_iterator(entry_list);
866 it != NULL;
867 it = ssh_list_get_iterator(entry_list)) {
868 struct ssh_knownhosts_entry *entry = NULL;
869
870 entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it);
871 ssh_knownhosts_entry_free(entry);
872 ssh_list_remove(entry_list, it);
873 }
874 ssh_list_free(entry_list);
875
876 return SSH_KNOWN_HOSTS_OK;
877 }
878
879 /**
880 * @brief Export the current session information to a known_hosts string.
881 *
882 * This exports the current information of a session which is connected so a
883 * ssh server into an entry line which can be added to a known_hosts file.
884 *
885 * @param[in] session The session with information to export.
886 *
887 * @param[in] pentry_string A pointer to a string to store the alloocated
888 * line of the entry. The user must free it using
889 * ssh_string_free_char().
890 *
891 * @return SSH_OK on succcess, SSH_ERROR otherwise.
892 */
ssh_session_export_known_hosts_entry(ssh_session session,char ** pentry_string)893 int ssh_session_export_known_hosts_entry(ssh_session session,
894 char **pentry_string)
895 {
896 ssh_key server_pubkey = NULL;
897 char *host = NULL;
898 char entry_buf[4096] = {0};
899 char *b64_key = NULL;
900 int rc;
901
902 if (pentry_string == NULL) {
903 ssh_set_error_invalid(session);
904 return SSH_ERROR;
905 }
906
907 if (session->opts.host == NULL) {
908 ssh_set_error(session, SSH_FATAL,
909 "Can't create known_hosts entry - hostname unknown");
910 return SSH_ERROR;
911 }
912
913 host = ssh_session_get_host_port(session);
914 if (host == NULL) {
915 return SSH_ERROR;
916 }
917
918 if (session->current_crypto == NULL) {
919 ssh_set_error(session, SSH_FATAL,
920 "No current crypto context, please connect first");
921 SAFE_FREE(host);
922 return SSH_ERROR;
923 }
924
925 server_pubkey = ssh_dh_get_current_server_publickey(session);
926 if (server_pubkey == NULL){
927 ssh_set_error(session, SSH_FATAL, "No public key present");
928 SAFE_FREE(host);
929 return SSH_ERROR;
930 }
931
932 rc = ssh_pki_export_pubkey_base64(server_pubkey, &b64_key);
933 if (rc < 0) {
934 SAFE_FREE(host);
935 return SSH_ERROR;
936 }
937
938 snprintf(entry_buf, sizeof(entry_buf),
939 "%s %s %s\n",
940 host,
941 server_pubkey->type_c,
942 b64_key);
943
944 SAFE_FREE(host);
945 SAFE_FREE(b64_key);
946
947 *pentry_string = strdup(entry_buf);
948 if (*pentry_string == NULL) {
949 return SSH_ERROR;
950 }
951
952 return SSH_OK;
953 }
954
955 /**
956 * @brief Add the current connected server to the user known_hosts file.
957 *
958 * This adds the currently connected server to the known_hosts file by
959 * appending a new line at the end. The global known_hosts file is considered
960 * read-only so it is not touched by this function.
961 *
962 * @param[in] session The session to use to write the entry.
963 *
964 * @return SSH_OK on success, SSH_ERROR otherwise.
965 */
ssh_session_update_known_hosts(ssh_session session)966 int ssh_session_update_known_hosts(ssh_session session)
967 {
968 FILE *fp = NULL;
969 char *entry = NULL;
970 char *dir = NULL;
971 size_t nwritten;
972 size_t len;
973 int rc;
974
975 if (session->opts.knownhosts == NULL) {
976 rc = ssh_options_apply(session);
977 if (rc != SSH_OK) {
978 ssh_set_error(session, SSH_FATAL, "Can't find a known_hosts file");
979 return SSH_ERROR;
980 }
981 }
982
983 errno = 0;
984 fp = fopen(session->opts.knownhosts, "a");
985 if (fp == NULL) {
986 if (errno == ENOENT) {
987 dir = ssh_dirname(session->opts.knownhosts);
988 if (dir == NULL) {
989 ssh_set_error(session, SSH_FATAL, "%s", strerror(errno));
990 return SSH_ERROR;
991 }
992
993 rc = ssh_mkdirs(dir, 0700);
994 if (rc < 0) {
995 ssh_set_error(session, SSH_FATAL,
996 "Cannot create %s directory: %s",
997 dir, strerror(errno));
998 SAFE_FREE(dir);
999 return SSH_ERROR;
1000 }
1001 SAFE_FREE(dir);
1002
1003 errno = 0;
1004 fp = fopen(session->opts.knownhosts, "a");
1005 if (fp == NULL) {
1006 ssh_set_error(session, SSH_FATAL,
1007 "Couldn't open known_hosts file %s"
1008 " for appending: %s",
1009 session->opts.knownhosts, strerror(errno));
1010 return SSH_ERROR;
1011 }
1012 } else {
1013 ssh_set_error(session, SSH_FATAL,
1014 "Couldn't open known_hosts file %s for appending: %s",
1015 session->opts.knownhosts, strerror(errno));
1016 return SSH_ERROR;
1017 }
1018 }
1019
1020 rc = ssh_session_export_known_hosts_entry(session, &entry);
1021 if (rc != SSH_OK) {
1022 fclose(fp);
1023 return rc;
1024 }
1025
1026 len = strlen(entry);
1027 nwritten = fwrite(entry, sizeof(char), len, fp);
1028 SAFE_FREE(entry);
1029 if (nwritten != len || ferror(fp)) {
1030 ssh_set_error(session, SSH_FATAL,
1031 "Couldn't append to known_hosts file %s: %s",
1032 session->opts.knownhosts, strerror(errno));
1033 fclose(fp);
1034 return SSH_ERROR;
1035 }
1036
1037 fclose(fp);
1038 return SSH_OK;
1039 }
1040
1041 static enum ssh_known_hosts_e
ssh_known_hosts_check_server_key(const char * hosts_entry,const char * filename,ssh_key server_key,struct ssh_knownhosts_entry ** pentry)1042 ssh_known_hosts_check_server_key(const char *hosts_entry,
1043 const char *filename,
1044 ssh_key server_key,
1045 struct ssh_knownhosts_entry **pentry)
1046 {
1047 struct ssh_list *entry_list = NULL;
1048 struct ssh_iterator *it = NULL;
1049 enum ssh_known_hosts_e found = SSH_KNOWN_HOSTS_UNKNOWN;
1050 int rc;
1051
1052 rc = ssh_known_hosts_read_entries(hosts_entry,
1053 filename,
1054 &entry_list);
1055 if (rc != 0) {
1056 ssh_list_free(entry_list);
1057 return SSH_KNOWN_HOSTS_UNKNOWN;
1058 }
1059
1060 it = ssh_list_get_iterator(entry_list);
1061 if (it == NULL) {
1062 ssh_list_free(entry_list);
1063 return SSH_KNOWN_HOSTS_UNKNOWN;
1064 }
1065
1066 for (;it != NULL; it = it->next) {
1067 struct ssh_knownhosts_entry *entry = NULL;
1068 int cmp;
1069
1070 entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it);
1071
1072 cmp = ssh_key_cmp(server_key, entry->publickey, SSH_KEY_CMP_PUBLIC);
1073 if (cmp == 0) {
1074 found = SSH_KNOWN_HOSTS_OK;
1075 if (pentry != NULL) {
1076 *pentry = entry;
1077 ssh_list_remove(entry_list, it);
1078 }
1079 break;
1080 }
1081
1082 if (ssh_key_type(server_key) == ssh_key_type(entry->publickey)) {
1083 found = SSH_KNOWN_HOSTS_CHANGED;
1084 continue;
1085 }
1086
1087 if (found != SSH_KNOWN_HOSTS_CHANGED) {
1088 found = SSH_KNOWN_HOSTS_OTHER;
1089 }
1090 }
1091
1092 for (it = ssh_list_get_iterator(entry_list);
1093 it != NULL;
1094 it = ssh_list_get_iterator(entry_list)) {
1095 struct ssh_knownhosts_entry *entry = NULL;
1096
1097 entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it);
1098 ssh_knownhosts_entry_free(entry);
1099 ssh_list_remove(entry_list, it);
1100 }
1101 ssh_list_free(entry_list);
1102
1103 return found;
1104 }
1105
1106 /**
1107 * @brief Get the known_hosts entry for the current connected session.
1108 *
1109 * @param[in] session The session to validate.
1110 *
1111 * @param[in] pentry A pointer to store the allocated known hosts entry.
1112 *
1113 * @returns SSH_KNOWN_HOSTS_OK: The server is known and has not changed.\n
1114 * SSH_KNOWN_HOSTS_CHANGED: The server key has changed. Either you
1115 * are under attack or the administrator
1116 * changed the key. You HAVE to warn the
1117 * user about a possible attack.\n
1118 * SSH_KNOWN_HOSTS_OTHER: The server gave use a key of a type while
1119 * we had an other type recorded. It is a
1120 * possible attack.\n
1121 * SSH_KNOWN_HOSTS_UNKNOWN: The server is unknown. User should
1122 * confirm the public key hash is correct.\n
1123 * SSH_KNOWN_HOSTS_NOT_FOUND: The known host file does not exist. The
1124 * host is thus unknown. File will be
1125 * created if host key is accepted.\n
1126 * SSH_KNOWN_HOSTS_ERROR: There had been an eror checking the host.
1127 *
1128 * @see ssh_knownhosts_entry_free()
1129 */
1130 enum ssh_known_hosts_e
ssh_session_get_known_hosts_entry(ssh_session session,struct ssh_knownhosts_entry ** pentry)1131 ssh_session_get_known_hosts_entry(ssh_session session,
1132 struct ssh_knownhosts_entry **pentry)
1133 {
1134 enum ssh_known_hosts_e old_rv, rv = SSH_KNOWN_HOSTS_UNKNOWN;
1135
1136 if (session->opts.knownhosts == NULL) {
1137 if (ssh_options_apply(session) < 0) {
1138 ssh_set_error(session,
1139 SSH_REQUEST_DENIED,
1140 "Can't find a known_hosts file");
1141
1142 return SSH_KNOWN_HOSTS_NOT_FOUND;
1143 }
1144 }
1145
1146 rv = ssh_session_get_known_hosts_entry_file(session,
1147 session->opts.knownhosts,
1148 pentry);
1149 if (rv == SSH_KNOWN_HOSTS_OK) {
1150 /* We already found a match in the first file: return */
1151 return rv;
1152 }
1153
1154 old_rv = rv;
1155 rv = ssh_session_get_known_hosts_entry_file(session,
1156 session->opts.global_knownhosts,
1157 pentry);
1158
1159 /* If we did not find any match at all: we report the previous result */
1160 if (rv == SSH_KNOWN_HOSTS_UNKNOWN) {
1161 if (session->opts.StrictHostKeyChecking == 0) {
1162 return SSH_KNOWN_HOSTS_OK;
1163 }
1164 return old_rv;
1165 }
1166
1167 /* We found some match: return it */
1168 return rv;
1169
1170 }
1171
1172 /**
1173 * @brief Get the known_hosts entry for the current connected session
1174 * from the given known_hosts file.
1175 *
1176 * @param[in] session The session to validate.
1177 *
1178 * @param[in] filename The filename to parse.
1179 *
1180 * @param[in] pentry A pointer to store the allocated known hosts entry.
1181 *
1182 * @returns SSH_KNOWN_HOSTS_OK: The server is known and has not changed.\n
1183 * SSH_KNOWN_HOSTS_CHANGED: The server key has changed. Either you
1184 * are under attack or the administrator
1185 * changed the key. You HAVE to warn the
1186 * user about a possible attack.\n
1187 * SSH_KNOWN_HOSTS_OTHER: The server gave use a key of a type while
1188 * we had an other type recorded. It is a
1189 * possible attack.\n
1190 * SSH_KNOWN_HOSTS_UNKNOWN: The server is unknown. User should
1191 * confirm the public key hash is correct.\n
1192 * SSH_KNOWN_HOSTS_NOT_FOUND: The known host file does not exist. The
1193 * host is thus unknown. File will be
1194 * created if host key is accepted.\n
1195 * SSH_KNOWN_HOSTS_ERROR: There had been an eror checking the host.
1196 *
1197 * @see ssh_knownhosts_entry_free()
1198 */
1199 enum ssh_known_hosts_e
ssh_session_get_known_hosts_entry_file(ssh_session session,const char * filename,struct ssh_knownhosts_entry ** pentry)1200 ssh_session_get_known_hosts_entry_file(ssh_session session,
1201 const char *filename,
1202 struct ssh_knownhosts_entry **pentry)
1203 {
1204 ssh_key server_pubkey = NULL;
1205 char *host_port = NULL;
1206 enum ssh_known_hosts_e found = SSH_KNOWN_HOSTS_UNKNOWN;
1207
1208 server_pubkey = ssh_dh_get_current_server_publickey(session);
1209 if (server_pubkey == NULL) {
1210 ssh_set_error(session,
1211 SSH_FATAL,
1212 "ssh_session_is_known_host called without a "
1213 "server_key!");
1214
1215 return SSH_KNOWN_HOSTS_ERROR;
1216 }
1217
1218 host_port = ssh_session_get_host_port(session);
1219 if (host_port == NULL) {
1220 return SSH_KNOWN_HOSTS_ERROR;
1221 }
1222
1223 found = ssh_known_hosts_check_server_key(host_port,
1224 filename,
1225 server_pubkey,
1226 pentry);
1227 SAFE_FREE(host_port);
1228
1229 return found;
1230 }
1231
1232 /**
1233 * @brief Check if the servers public key for the connected session is known.
1234 *
1235 * This checks if we already know the public key of the server we want to
1236 * connect to. This allows to detect if there is a MITM attach going on
1237 * of if there have been changes on the server we don't know about.
1238 *
1239 * @param[in] session The SSH to validate.
1240 *
1241 * @returns SSH_KNOWN_HOSTS_OK: The server is known and has not changed.\n
1242 * SSH_KNOWN_HOSTS_CHANGED: The server key has changed. Either you
1243 * are under attack or the administrator
1244 * changed the key. You HAVE to warn the
1245 * user about a possible attack.\n
1246 * SSH_KNOWN_HOSTS_OTHER: The server gave use a key of a type while
1247 * we had an other type recorded. It is a
1248 * possible attack.\n
1249 * SSH_KNOWN_HOSTS_UNKNOWN: The server is unknown. User should
1250 * confirm the public key hash is correct.\n
1251 * SSH_KNOWN_HOSTS_NOT_FOUND: The known host file does not exist. The
1252 * host is thus unknown. File will be
1253 * created if host key is accepted.\n
1254 * SSH_KNOWN_HOSTS_ERROR: There had been an error checking the host.
1255 */
ssh_session_is_known_server(ssh_session session)1256 enum ssh_known_hosts_e ssh_session_is_known_server(ssh_session session)
1257 {
1258 return ssh_session_get_known_hosts_entry(session, NULL);
1259 }
1260
1261 /** @} */
1262