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