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