diff options
author | Colin Watson <cjwatson@debian.org> | 2011-01-24 12:43:25 +0000 |
---|---|---|
committer | Colin Watson <cjwatson@debian.org> | 2011-01-24 12:43:25 +0000 |
commit | 626f1d986ff72aa514da63e34744e1de9cf21b9a (patch) | |
tree | d215a5280bc2e57251e4a9e08bfd3674ad824a94 /hostfile.c | |
parent | 6ed622cb6fe8f71bbe0d998cdd12280410bfb420 (diff) | |
parent | 0970072c89b079b022538e3c366fbfa2c53fc821 (diff) |
* New upstream release (http://www.openssh.org/txt/release-5.7):
- Implement Elliptic Curve Cryptography modes for key exchange (ECDH)
and host/user keys (ECDSA) as specified by RFC5656. ECDH and ECDSA
offer better performance than plain DH and DSA at the same equivalent
symmetric key length, as well as much shorter keys.
- sftp(1)/sftp-server(8): add a protocol extension to support a hard
link operation. It is available through the "ln" command in the
client. The old "ln" behaviour of creating a symlink is available
using its "-s" option or through the preexisting "symlink" command.
- scp(1): Add a new -3 option to scp: Copies between two remote hosts
are transferred through the local host (closes: #508613).
- ssh(1): "atomically" create the listening mux socket by binding it on
a temporary name and then linking it into position after listen() has
succeeded. This allows the mux clients to determine that the server
socket is either ready or stale without races (closes: #454784).
Stale server sockets are now automatically removed (closes: #523250).
- ssh(1): install a SIGCHLD handler to reap expired child process
(closes: #594687).
- ssh(1)/ssh-agent(1): honour $TMPDIR for client xauth and ssh-agent
temporary directories (closes: #357469, although only if you arrange
for ssh-agent to actually see $TMPDIR since the setgid bit will cause
it to be stripped off).
Diffstat (limited to 'hostfile.c')
-rw-r--r-- | hostfile.c | 302 |
1 files changed, 177 insertions, 125 deletions
diff --git a/hostfile.c b/hostfile.c index afab6dad1..b6f924b23 100644 --- a/hostfile.c +++ b/hostfile.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: hostfile.c,v 1.48 2010/03/04 10:36:03 djm Exp $ */ | 1 | /* $OpenBSD: hostfile.c,v 1.50 2010/12/04 13:31:37 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland | 4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
@@ -56,6 +56,12 @@ | |||
56 | #include "key.h" | 56 | #include "key.h" |
57 | #include "hostfile.h" | 57 | #include "hostfile.h" |
58 | #include "log.h" | 58 | #include "log.h" |
59 | #include "misc.h" | ||
60 | |||
61 | struct hostkeys { | ||
62 | struct hostkey_entry *entries; | ||
63 | u_int num_entries; | ||
64 | }; | ||
59 | 65 | ||
60 | static int | 66 | static int |
61 | extract_salt(const char *s, u_int l, char *salt, size_t salt_len) | 67 | extract_salt(const char *s, u_int l, char *salt, size_t salt_len) |
@@ -164,26 +170,28 @@ hostfile_read_key(char **cpp, u_int *bitsp, Key *ret) | |||
164 | 170 | ||
165 | /* Return results. */ | 171 | /* Return results. */ |
166 | *cpp = cp; | 172 | *cpp = cp; |
167 | *bitsp = key_size(ret); | 173 | if (bitsp != NULL) |
174 | *bitsp = key_size(ret); | ||
168 | return 1; | 175 | return 1; |
169 | } | 176 | } |
170 | 177 | ||
171 | static int | 178 | static int |
172 | hostfile_check_key(int bits, const Key *key, const char *host, const char *filename, int linenum) | 179 | hostfile_check_key(int bits, const Key *key, const char *host, |
180 | const char *filename, u_long linenum) | ||
173 | { | 181 | { |
174 | if (key == NULL || key->type != KEY_RSA1 || key->rsa == NULL) | 182 | if (key == NULL || key->type != KEY_RSA1 || key->rsa == NULL) |
175 | return 1; | 183 | return 1; |
176 | if (bits != BN_num_bits(key->rsa->n)) { | 184 | if (bits != BN_num_bits(key->rsa->n)) { |
177 | logit("Warning: %s, line %d: keysize mismatch for host %s: " | 185 | logit("Warning: %s, line %lu: keysize mismatch for host %s: " |
178 | "actual %d vs. announced %d.", | 186 | "actual %d vs. announced %d.", |
179 | filename, linenum, host, BN_num_bits(key->rsa->n), bits); | 187 | filename, linenum, host, BN_num_bits(key->rsa->n), bits); |
180 | logit("Warning: replace %d with %d in %s, line %d.", | 188 | logit("Warning: replace %d with %d in %s, line %lu.", |
181 | bits, BN_num_bits(key->rsa->n), filename, linenum); | 189 | bits, BN_num_bits(key->rsa->n), filename, linenum); |
182 | } | 190 | } |
183 | return 1; | 191 | return 1; |
184 | } | 192 | } |
185 | 193 | ||
186 | static enum { MRK_ERROR, MRK_NONE, MRK_REVOKE, MRK_CA } | 194 | static HostkeyMarker |
187 | check_markers(char **cpp) | 195 | check_markers(char **cpp) |
188 | { | 196 | { |
189 | char marker[32], *sp, *cp = *cpp; | 197 | char marker[32], *sp, *cp = *cpp; |
@@ -218,49 +226,32 @@ check_markers(char **cpp) | |||
218 | return ret; | 226 | return ret; |
219 | } | 227 | } |
220 | 228 | ||
221 | /* | 229 | struct hostkeys * |
222 | * Checks whether the given host (which must be in all lowercase) is already | 230 | init_hostkeys(void) |
223 | * in the list of our known hosts. Returns HOST_OK if the host is known and | 231 | { |
224 | * has the specified key, HOST_NEW if the host is not known, and HOST_CHANGED | 232 | struct hostkeys *ret = xcalloc(1, sizeof(*ret)); |
225 | * if the host is known but used to have a different host key. | ||
226 | * | ||
227 | * If no 'key' has been specified and a key of type 'keytype' is known | ||
228 | * for the specified host, then HOST_FOUND is returned. | ||
229 | */ | ||
230 | 233 | ||
231 | static HostStatus | 234 | ret->entries = NULL; |
232 | check_host_in_hostfile_by_key_or_type(const char *filename, | 235 | return ret; |
233 | const char *host, const Key *key, int keytype, Key *found, | 236 | } |
234 | int want_revocation, int *numret) | 237 | |
238 | void | ||
239 | load_hostkeys(struct hostkeys *hostkeys, const char *host, const char *path) | ||
235 | { | 240 | { |
236 | FILE *f; | 241 | FILE *f; |
237 | char line[8192]; | 242 | char line[8192]; |
238 | int want, have, linenum = 0, want_cert = key_is_cert(key); | 243 | u_long linenum = 0, num_loaded = 0; |
239 | u_int kbits; | ||
240 | char *cp, *cp2, *hashed_host; | 244 | char *cp, *cp2, *hashed_host; |
241 | HostStatus end_return; | 245 | HostkeyMarker marker; |
242 | 246 | Key *key; | |
243 | debug3("check_host_in_hostfile: host %s filename %s", host, filename); | 247 | int kbits; |
244 | 248 | ||
245 | if (want_revocation && (key == NULL || keytype != 0 || found != NULL)) | 249 | if ((f = fopen(path, "r")) == NULL) |
246 | fatal("%s: invalid arguments", __func__); | 250 | return; |
247 | 251 | debug3("%s: loading entries for host \"%.100s\" from file \"%s\"", | |
248 | /* Open the file containing the list of known hosts. */ | 252 | __func__, host, path); |
249 | f = fopen(filename, "r"); | 253 | while (read_keyfile_line(f, path, line, sizeof(line), &linenum) == 0) { |
250 | if (!f) | ||
251 | return HOST_NEW; | ||
252 | |||
253 | /* | ||
254 | * Return value when the loop terminates. This is set to | ||
255 | * HOST_CHANGED if we have seen a different key for the host and have | ||
256 | * not found the proper one. | ||
257 | */ | ||
258 | end_return = HOST_NEW; | ||
259 | |||
260 | /* Go through the file. */ | ||
261 | while (fgets(line, sizeof(line), f)) { | ||
262 | cp = line; | 254 | cp = line; |
263 | linenum++; | ||
264 | 255 | ||
265 | /* Skip any leading whitespace, comments and empty lines. */ | 256 | /* Skip any leading whitespace, comments and empty lines. */ |
266 | for (; *cp == ' ' || *cp == '\t'; cp++) | 257 | for (; *cp == ' ' || *cp == '\t'; cp++) |
@@ -268,19 +259,11 @@ check_host_in_hostfile_by_key_or_type(const char *filename, | |||
268 | if (!*cp || *cp == '#' || *cp == '\n') | 259 | if (!*cp || *cp == '#' || *cp == '\n') |
269 | continue; | 260 | continue; |
270 | 261 | ||
271 | if (want_revocation) | 262 | if ((marker = check_markers(&cp)) == MRK_ERROR) { |
272 | want = MRK_REVOKE; | 263 | verbose("%s: invalid marker at %s:%lu", |
273 | else if (want_cert) | 264 | __func__, path, linenum); |
274 | want = MRK_CA; | ||
275 | else | ||
276 | want = MRK_NONE; | ||
277 | |||
278 | if ((have = check_markers(&cp)) == MRK_ERROR) { | ||
279 | verbose("%s: invalid marker at %s:%d", | ||
280 | __func__, filename, linenum); | ||
281 | continue; | ||
282 | } else if (want != have) | ||
283 | continue; | 265 | continue; |
266 | } | ||
284 | 267 | ||
285 | /* Find the end of the host name portion. */ | 268 | /* Find the end of the host name portion. */ |
286 | for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++) | 269 | for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++) |
@@ -292,8 +275,8 @@ check_host_in_hostfile_by_key_or_type(const char *filename, | |||
292 | continue; | 275 | continue; |
293 | hashed_host = host_hash(host, cp, (u_int) (cp2 - cp)); | 276 | hashed_host = host_hash(host, cp, (u_int) (cp2 - cp)); |
294 | if (hashed_host == NULL) { | 277 | if (hashed_host == NULL) { |
295 | debug("Invalid hashed host line %d of %s", | 278 | debug("Invalid hashed host line %lu of %s", |
296 | linenum, filename); | 279 | linenum, path); |
297 | continue; | 280 | continue; |
298 | } | 281 | } |
299 | if (strncmp(hashed_host, cp, (u_int) (cp2 - cp)) != 0) | 282 | if (strncmp(hashed_host, cp, (u_int) (cp2 - cp)) != 0) |
@@ -303,98 +286,167 @@ check_host_in_hostfile_by_key_or_type(const char *filename, | |||
303 | /* Got a match. Skip host name. */ | 286 | /* Got a match. Skip host name. */ |
304 | cp = cp2; | 287 | cp = cp2; |
305 | 288 | ||
306 | if (want_revocation) | ||
307 | found = key_new(KEY_UNSPEC); | ||
308 | |||
309 | /* | 289 | /* |
310 | * Extract the key from the line. This will skip any leading | 290 | * Extract the key from the line. This will skip any leading |
311 | * whitespace. Ignore badly formatted lines. | 291 | * whitespace. Ignore badly formatted lines. |
312 | */ | 292 | */ |
313 | if (!hostfile_read_key(&cp, &kbits, found)) | 293 | key = key_new(KEY_UNSPEC); |
294 | if (!hostfile_read_key(&cp, &kbits, key)) { | ||
295 | key_free(key); | ||
296 | key = key_new(KEY_RSA1); | ||
297 | if (!hostfile_read_key(&cp, &kbits, key)) { | ||
298 | key_free(key); | ||
299 | continue; | ||
300 | } | ||
301 | } | ||
302 | if (!hostfile_check_key(kbits, key, host, path, linenum)) | ||
314 | continue; | 303 | continue; |
315 | 304 | ||
316 | if (numret != NULL) | 305 | debug3("%s: found %skey type %s in file %s:%lu", __func__, |
317 | *numret = linenum; | 306 | marker == MRK_NONE ? "" : |
307 | (marker == MRK_CA ? "ca " : "revoked "), | ||
308 | key_type(key), path, linenum); | ||
309 | hostkeys->entries = xrealloc(hostkeys->entries, | ||
310 | hostkeys->num_entries + 1, sizeof(*hostkeys->entries)); | ||
311 | hostkeys->entries[hostkeys->num_entries].host = xstrdup(host); | ||
312 | hostkeys->entries[hostkeys->num_entries].file = xstrdup(path); | ||
313 | hostkeys->entries[hostkeys->num_entries].line = linenum; | ||
314 | hostkeys->entries[hostkeys->num_entries].key = key; | ||
315 | hostkeys->entries[hostkeys->num_entries].marker = marker; | ||
316 | hostkeys->num_entries++; | ||
317 | num_loaded++; | ||
318 | } | ||
319 | debug3("%s: loaded %lu keys", __func__, num_loaded); | ||
320 | fclose(f); | ||
321 | return; | ||
322 | } | ||
318 | 323 | ||
319 | if (key == NULL) { | 324 | void |
320 | /* we found a key of the requested type */ | 325 | free_hostkeys(struct hostkeys *hostkeys) |
321 | if (found->type == keytype) { | 326 | { |
322 | fclose(f); | 327 | u_int i; |
323 | return HOST_FOUND; | 328 | |
324 | } | 329 | for (i = 0; i < hostkeys->num_entries; i++) { |
325 | continue; | 330 | xfree(hostkeys->entries[i].host); |
326 | } | 331 | xfree(hostkeys->entries[i].file); |
332 | key_free(hostkeys->entries[i].key); | ||
333 | bzero(hostkeys->entries + i, sizeof(*hostkeys->entries)); | ||
334 | } | ||
335 | if (hostkeys->entries != NULL) | ||
336 | xfree(hostkeys->entries); | ||
337 | hostkeys->entries = NULL; | ||
338 | hostkeys->num_entries = 0; | ||
339 | xfree(hostkeys); | ||
340 | } | ||
341 | |||
342 | static int | ||
343 | check_key_not_revoked(struct hostkeys *hostkeys, Key *k) | ||
344 | { | ||
345 | int is_cert = key_is_cert(k); | ||
346 | u_int i; | ||
327 | 347 | ||
328 | if (!hostfile_check_key(kbits, found, host, filename, linenum)) | 348 | for (i = 0; i < hostkeys->num_entries; i++) { |
349 | if (hostkeys->entries[i].marker != MRK_REVOKE) | ||
329 | continue; | 350 | continue; |
351 | if (key_equal_public(k, hostkeys->entries[i].key)) | ||
352 | return -1; | ||
353 | if (is_cert && | ||
354 | key_equal_public(k->cert->signature_key, | ||
355 | hostkeys->entries[i].key)) | ||
356 | return -1; | ||
357 | } | ||
358 | return 0; | ||
359 | } | ||
330 | 360 | ||
331 | if (want_revocation) { | 361 | /* |
332 | if (key_is_cert(key) && | 362 | * Match keys against a specified key, or look one up by key type. |
333 | key_equal_public(key->cert->signature_key, found)) { | 363 | * |
334 | verbose("check_host_in_hostfile: revoked CA " | 364 | * If looking for a keytype (key == NULL) and one is found then return |
335 | "line %d", linenum); | 365 | * HOST_FOUND, otherwise HOST_NEW. |
336 | key_free(found); | 366 | * |
337 | return HOST_REVOKED; | 367 | * If looking for a key (key != NULL): |
338 | } | 368 | * 1. If the key is a cert and a matching CA is found, return HOST_OK |
339 | if (key_equal_public(key, found)) { | 369 | * 2. If the key is not a cert and a matching key is found, return HOST_OK |
340 | verbose("check_host_in_hostfile: revoked key " | 370 | * 3. If no key matches but a key with a different type is found, then |
341 | "line %d", linenum); | 371 | * return HOST_CHANGED |
342 | key_free(found); | 372 | * 4. If no matching keys are found, then return HOST_NEW. |
343 | return HOST_REVOKED; | 373 | * |
344 | } | 374 | * Finally, check any found key is not revoked. |
345 | key_free(found); | 375 | */ |
376 | static HostStatus | ||
377 | check_hostkeys_by_key_or_type(struct hostkeys *hostkeys, | ||
378 | Key *k, int keytype, const struct hostkey_entry **found) | ||
379 | { | ||
380 | u_int i; | ||
381 | HostStatus end_return = HOST_NEW; | ||
382 | int want_cert = key_is_cert(k); | ||
383 | HostkeyMarker want_marker = want_cert ? MRK_CA : MRK_NONE; | ||
384 | int proto = (k ? k->type : keytype) == KEY_RSA1 ? 1 : 2; | ||
385 | |||
386 | if (found != NULL) | ||
387 | *found = NULL; | ||
388 | |||
389 | for (i = 0; i < hostkeys->num_entries; i++) { | ||
390 | if (proto == 1 && hostkeys->entries[i].key->type != KEY_RSA1) | ||
391 | continue; | ||
392 | if (proto == 2 && hostkeys->entries[i].key->type == KEY_RSA1) | ||
346 | continue; | 393 | continue; |
394 | if (hostkeys->entries[i].marker != want_marker) | ||
395 | continue; | ||
396 | if (k == NULL) { | ||
397 | if (hostkeys->entries[i].key->type != keytype) | ||
398 | continue; | ||
399 | end_return = HOST_FOUND; | ||
400 | if (found != NULL) | ||
401 | *found = hostkeys->entries + i; | ||
402 | k = hostkeys->entries[i].key; | ||
403 | break; | ||
347 | } | 404 | } |
348 | 405 | if (want_cert) { | |
349 | /* Check if the current key is the same as the given key. */ | 406 | if (key_equal_public(k->cert->signature_key, |
350 | if (want_cert && key_equal(key->cert->signature_key, found)) { | 407 | hostkeys->entries[i].key)) { |
351 | /* Found CA cert for key */ | 408 | /* A matching CA exists */ |
352 | debug3("check_host_in_hostfile: CA match line %d", | 409 | end_return = HOST_OK; |
353 | linenum); | 410 | if (found != NULL) |
354 | fclose(f); | 411 | *found = hostkeys->entries + i; |
355 | return HOST_OK; | 412 | break; |
356 | } else if (!want_cert && key_equal(key, found)) { | 413 | } |
357 | /* Found identical key */ | 414 | } else { |
358 | debug3("check_host_in_hostfile: match line %d", linenum); | 415 | if (key_equal(k, hostkeys->entries[i].key)) { |
359 | fclose(f); | 416 | end_return = HOST_OK; |
360 | return HOST_OK; | 417 | if (found != NULL) |
418 | *found = hostkeys->entries + i; | ||
419 | break; | ||
420 | } | ||
421 | /* A non-maching key exists */ | ||
422 | end_return = HOST_CHANGED; | ||
423 | if (found != NULL) | ||
424 | *found = hostkeys->entries + i; | ||
361 | } | 425 | } |
362 | /* | ||
363 | * They do not match. We will continue to go through the | ||
364 | * file; however, we note that we will not return that it is | ||
365 | * new. | ||
366 | */ | ||
367 | end_return = HOST_CHANGED; | ||
368 | } | 426 | } |
369 | /* Clear variables and close the file. */ | 427 | if (check_key_not_revoked(hostkeys, k) != 0) { |
370 | fclose(f); | 428 | end_return = HOST_REVOKED; |
371 | 429 | if (found != NULL) | |
372 | /* | 430 | *found = NULL; |
373 | * Return either HOST_NEW or HOST_CHANGED, depending on whether we | 431 | } |
374 | * saw a different key for the host. | ||
375 | */ | ||
376 | return end_return; | 432 | return end_return; |
377 | } | 433 | } |
378 | 434 | ||
379 | HostStatus | 435 | HostStatus |
380 | check_host_in_hostfile(const char *filename, const char *host, const Key *key, | 436 | check_key_in_hostkeys(struct hostkeys *hostkeys, Key *key, |
381 | Key *found, int *numret) | 437 | const struct hostkey_entry **found) |
382 | { | 438 | { |
383 | if (key == NULL) | 439 | if (key == NULL) |
384 | fatal("no key to look up"); | 440 | fatal("no key to look up"); |
385 | if (check_host_in_hostfile_by_key_or_type(filename, host, | 441 | return check_hostkeys_by_key_or_type(hostkeys, key, 0, found); |
386 | key, 0, NULL, 1, NULL) == HOST_REVOKED) | ||
387 | return HOST_REVOKED; | ||
388 | return check_host_in_hostfile_by_key_or_type(filename, host, key, 0, | ||
389 | found, 0, numret); | ||
390 | } | 442 | } |
391 | 443 | ||
392 | int | 444 | int |
393 | lookup_key_in_hostfile_by_type(const char *filename, const char *host, | 445 | lookup_key_in_hostkeys_by_type(struct hostkeys *hostkeys, int keytype, |
394 | int keytype, Key *found, int *numret) | 446 | const struct hostkey_entry **found) |
395 | { | 447 | { |
396 | return (check_host_in_hostfile_by_key_or_type(filename, host, NULL, | 448 | return (check_hostkeys_by_key_or_type(hostkeys, NULL, keytype, |
397 | keytype, found, 0, numret) == HOST_FOUND); | 449 | found) == HOST_FOUND); |
398 | } | 450 | } |
399 | 451 | ||
400 | /* | 452 | /* |