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