diff options
author | Ed Maste <emaste@FreeBSD.org> | 2021-09-08 01:05:51 +0000 |
---|---|---|
committer | Ed Maste <emaste@FreeBSD.org> | 2021-09-08 01:05:51 +0000 |
commit | 19261079b74319502c6ffa1249920079f0f69a72 (patch) | |
tree | a07fb2205e0cea7dee1ffbcc945d9d5b97124714 /crypto/openssh/hostfile.c | |
parent | c5128c48df3c2f3828432aff2ea536bb9c887e14 (diff) | |
parent | 66719ee573ac2290622db642f6e89ab35b179f3d (diff) | |
download | src-19261079b74319502c6ffa1249920079f0f69a72.tar.gz src-19261079b74319502c6ffa1249920079f0f69a72.zip |
openssh: update to OpenSSH v8.7p1
Some notable changes, from upstream's release notes:
- sshd(8): Remove support for obsolete "host/port" syntax.
- ssh(1): When prompting whether to record a new host key, accept the key
fingerprint as a synonym for "yes".
- ssh-keygen(1): when acting as a CA and signing certificates with an RSA
key, default to using the rsa-sha2-512 signature algorithm.
- ssh(1), sshd(8), ssh-keygen(1): this release removes the "ssh-rsa"
(RSA/SHA1) algorithm from those accepted for certificate signatures.
- ssh-sk-helper(8): this is a new binary. It is used by the FIDO/U2F
support to provide address-space isolation for token middleware
libraries (including the internal one).
- ssh(1): this release enables UpdateHostkeys by default subject to some
conservative preconditions.
- scp(1): this release changes the behaviour of remote to remote copies
(e.g. "scp host-a:/path host-b:") to transfer through the local host
by default.
- scp(1): experimental support for transfers using the SFTP protocol as
a replacement for the venerable SCP/RCP protocol that it has
traditionally used.
Additional integration work is needed to support FIDO/U2F in the base
system.
Deprecation Notice
------------------
OpenSSH will disable the ssh-rsa signature scheme by default in the
next release.
Reviewed by: imp
MFC after: 1 month
Relnotes: Yes
Sponsored by: The FreeBSD Foundation
Differential Revision: https://reviews.freebsd.org/D29985
Diffstat (limited to 'crypto/openssh/hostfile.c')
-rw-r--r-- | crypto/openssh/hostfile.c | 269 |
1 files changed, 185 insertions, 84 deletions
diff --git a/crypto/openssh/hostfile.c b/crypto/openssh/hostfile.c index e1f826bddc9e..ce00cd713549 100644 --- a/crypto/openssh/hostfile.c +++ b/crypto/openssh/hostfile.c @@ -1,4 +1,4 @@ -/* $OpenBSD: hostfile.c,v 1.73 2018/07/16 03:09:13 djm Exp $ */ +/* $OpenBSD: hostfile.c,v 1.91 2021/07/05 01:16:46 dtucker Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -49,7 +49,6 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <stdarg.h> #include <unistd.h> #include "xmalloc.h" @@ -58,14 +57,11 @@ #include "hostfile.h" #include "log.h" #include "misc.h" +#include "pathnames.h" #include "ssherr.h" #include "digest.h" #include "hmac.h" - -struct hostkeys { - struct hostkey_entry *entries; - u_int num_entries; -}; +#include "sshbuf.h" /* XXX hmac is too easy to dictionary attack; use bcrypt? */ @@ -141,12 +137,12 @@ host_hash(const char *host, const char *name_from_hostfile, u_int src_len) ssh_hmac_init(ctx, salt, len) < 0 || ssh_hmac_update(ctx, host, strlen(host)) < 0 || ssh_hmac_final(ctx, result, sizeof(result))) - fatal("%s: ssh_hmac failed", __func__); + fatal_f("ssh_hmac failed"); ssh_hmac_free(ctx); if (__b64_ntop(salt, len, uu_salt, sizeof(uu_salt)) == -1 || __b64_ntop(result, len, uu_result, sizeof(uu_result)) == -1) - fatal("%s: __b64_ntop failed", __func__); + fatal_f("__b64_ntop failed"); snprintf(encoded, sizeof(encoded), "%s%s%c%s", HASH_MAGIC, uu_salt, HASH_DELIM, uu_result); @@ -163,13 +159,12 @@ int hostfile_read_key(char **cpp, u_int *bitsp, struct sshkey *ret) { char *cp; - int r; /* Skip leading whitespace. */ for (cp = *cpp; *cp == ' ' || *cp == '\t'; cp++) ; - if ((r = sshkey_read(ret, &cp)) != 0) + if (sshkey_read(ret, &cp) != 0) return 0; /* Skip trailing whitespace. */ @@ -247,7 +242,7 @@ record_hostkey(struct hostkey_foreach_line *l, void *_ctx) return 0; } - debug3("%s: found %skey type %s in file %s:%lu", __func__, + debug3_f("found %skey type %s in file %s:%lu", l->marker == MRK_NONE ? "" : (l->marker == MRK_CA ? "ca " : "revoked "), sshkey_type(l->key), l->path, l->linenum); @@ -261,6 +256,7 @@ record_hostkey(struct hostkey_foreach_line *l, void *_ctx) hostkeys->entries[hostkeys->num_entries].key = l->key; l->key = NULL; /* steal it */ hostkeys->entries[hostkeys->num_entries].marker = l->marker; + hostkeys->entries[hostkeys->num_entries].note = l->note; hostkeys->num_entries++; ctx->num_loaded++; @@ -268,7 +264,8 @@ record_hostkey(struct hostkey_foreach_line *l, void *_ctx) } void -load_hostkeys(struct hostkeys *hostkeys, const char *host, const char *path) +load_hostkeys_file(struct hostkeys *hostkeys, const char *host, + const char *path, FILE *f, u_int note) { int r; struct load_callback_ctx ctx; @@ -277,15 +274,28 @@ load_hostkeys(struct hostkeys *hostkeys, const char *host, const char *path) ctx.num_loaded = 0; ctx.hostkeys = hostkeys; - if ((r = hostkeys_foreach(path, record_hostkey, &ctx, host, NULL, - HKF_WANT_MATCH|HKF_WANT_PARSE_KEY)) != 0) { + if ((r = hostkeys_foreach_file(path, f, record_hostkey, &ctx, host, + NULL, HKF_WANT_MATCH|HKF_WANT_PARSE_KEY, note)) != 0) { if (r != SSH_ERR_SYSTEM_ERROR && errno != ENOENT) - debug("%s: hostkeys_foreach failed for %s: %s", - __func__, path, ssh_err(r)); + debug_fr(r, "hostkeys_foreach failed for %s", path); } if (ctx.num_loaded != 0) - debug3("%s: loaded %lu keys from %s", __func__, - ctx.num_loaded, host); + debug3_f("loaded %lu keys from %s", ctx.num_loaded, host); +} + +void +load_hostkeys(struct hostkeys *hostkeys, const char *host, const char *path, + u_int note) +{ + FILE *f; + + if ((f = fopen(path, "r")) == NULL) { + debug_f("fopen %s: %s", path, strerror(errno)); + return; + } + + load_hostkeys_file(hostkeys, host, path, f, note); + fclose(f); } void @@ -300,8 +310,7 @@ free_hostkeys(struct hostkeys *hostkeys) explicit_bzero(hostkeys->entries + i, sizeof(*hostkeys->entries)); } free(hostkeys->entries); - explicit_bzero(hostkeys, sizeof(*hostkeys)); - free(hostkeys); + freezero(hostkeys, sizeof(*hostkeys)); } static int @@ -315,7 +324,7 @@ check_key_not_revoked(struct hostkeys *hostkeys, struct sshkey *k) continue; if (sshkey_equal_public(k, hostkeys->entries[i].key)) return -1; - if (is_cert && + if (is_cert && k != NULL && sshkey_equal_public(k->cert->signature_key, hostkeys->entries[i].key)) return -1; @@ -340,7 +349,7 @@ check_key_not_revoked(struct hostkeys *hostkeys, struct sshkey *k) */ static HostStatus check_hostkeys_by_key_or_type(struct hostkeys *hostkeys, - struct sshkey *k, int keytype, const struct hostkey_entry **found) + struct sshkey *k, int keytype, int nid, const struct hostkey_entry **found) { u_int i; HostStatus end_return = HOST_NEW; @@ -356,6 +365,10 @@ check_hostkeys_by_key_or_type(struct hostkeys *hostkeys, if (k == NULL) { if (hostkeys->entries[i].key->type != keytype) continue; + if (nid != -1 && + sshkey_type_plain(keytype) == KEY_ECDSA && + hostkeys->entries[i].key->ecdsa_nid != nid) + continue; end_return = HOST_FOUND; if (found != NULL) *found = hostkeys->entries + i; @@ -378,7 +391,7 @@ check_hostkeys_by_key_or_type(struct hostkeys *hostkeys, *found = hostkeys->entries + i; break; } - /* A non-maching key exists */ + /* A non-matching key exists */ end_return = HOST_CHANGED; if (found != NULL) *found = hostkeys->entries + i; @@ -398,17 +411,29 @@ check_key_in_hostkeys(struct hostkeys *hostkeys, struct sshkey *key, { if (key == NULL) fatal("no key to look up"); - return check_hostkeys_by_key_or_type(hostkeys, key, 0, found); + return check_hostkeys_by_key_or_type(hostkeys, key, 0, -1, found); } int -lookup_key_in_hostkeys_by_type(struct hostkeys *hostkeys, int keytype, +lookup_key_in_hostkeys_by_type(struct hostkeys *hostkeys, int keytype, int nid, const struct hostkey_entry **found) { - return (check_hostkeys_by_key_or_type(hostkeys, NULL, keytype, + return (check_hostkeys_by_key_or_type(hostkeys, NULL, keytype, nid, found) == HOST_FOUND); } +int +lookup_marker_in_hostkeys(struct hostkeys *hostkeys, int want_marker) +{ + u_int i; + + for (i = 0; i < hostkeys->num_entries; i++) { + if (hostkeys->entries[i].marker == (HostkeyMarker)want_marker) + return 1; + } + return 0; +} + static int write_host_entry(FILE *f, const char *host, const char *ip, const struct sshkey *key, int store_hash) @@ -421,7 +446,7 @@ write_host_entry(FILE *f, const char *host, const char *ip, if (store_hash) { if ((hashed_host = host_hash(lhost, NULL, 0)) == NULL) { - error("%s: host_hash failed", __func__); + error_f("host_hash failed"); free(lhost); return 0; } @@ -435,12 +460,53 @@ write_host_entry(FILE *f, const char *host, const char *ip, if ((r = sshkey_write(key, f)) == 0) success = 1; else - error("%s: sshkey_write failed: %s", __func__, ssh_err(r)); + error_fr(r, "sshkey_write"); fputc('\n', f); + /* If hashing is enabled, the IP address needs to go on its own line */ + if (success && store_hash && ip != NULL) + success = write_host_entry(f, ip, NULL, key, 1); return success; } /* + * Create user ~/.ssh directory if it doesn't exist and we want to write to it. + * If notify is set, a message will be emitted if the directory is created. + */ +void +hostfile_create_user_ssh_dir(const char *filename, int notify) +{ + char *dotsshdir = NULL, *p; + size_t len; + struct stat st; + + if ((p = strrchr(filename, '/')) == NULL) + return; + len = p - filename; + dotsshdir = tilde_expand_filename("~/" _PATH_SSH_USER_DIR, getuid()); + if (strlen(dotsshdir) > len || strncmp(filename, dotsshdir, len) != 0) + goto out; /* not ~/.ssh prefixed */ + if (stat(dotsshdir, &st) == 0) + goto out; /* dir already exists */ + else if (errno != ENOENT) + error("Could not stat %s: %s", dotsshdir, strerror(errno)); + else { +#ifdef WITH_SELINUX + ssh_selinux_setfscreatecon(dotsshdir); +#endif + if (mkdir(dotsshdir, 0700) == -1) + error("Could not create directory '%.200s' (%s).", + dotsshdir, strerror(errno)); + else if (notify) + logit("Created directory '%s'.", dotsshdir); +#ifdef WITH_SELINUX + ssh_selinux_setfscreatecon(NULL); +#endif + } + out: + free(dotsshdir); +} + +/* * Appends an entry to the host file. Returns false if the entry could not * be appended. */ @@ -453,6 +519,7 @@ add_host_to_hostfile(const char *filename, const char *host, if (key == NULL) return 1; /* XXX ? */ + hostfile_create_user_ssh_dir(filename, 0); f = fopen(filename, "a"); if (!f) return 0; @@ -464,8 +531,8 @@ add_host_to_hostfile(const char *filename, const char *host, struct host_delete_ctx { FILE *out; int quiet; - const char *host; - int *skip_keys; /* XXX split for host/ip? might want to ensure both */ + const char *host, *ip; + u_int *match_keys; /* mask of HKF_MATCH_* for this key */ struct sshkey * const *keys; size_t nkeys; int modified; @@ -478,26 +545,21 @@ host_delete(struct hostkey_foreach_line *l, void *_ctx) int loglevel = ctx->quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_VERBOSE; size_t i; - if (l->status == HKF_STATUS_MATCHED) { - if (l->marker != MRK_NONE) { - /* Don't remove CA and revocation lines */ - fprintf(ctx->out, "%s\n", l->line); - return 0; - } - + /* Don't remove CA and revocation lines */ + if (l->status == HKF_STATUS_MATCHED && l->marker == MRK_NONE) { /* * If this line contains one of the keys that we will be * adding later, then don't change it and mark the key for * skipping. */ for (i = 0; i < ctx->nkeys; i++) { - if (sshkey_equal(ctx->keys[i], l->key)) { - ctx->skip_keys[i] = 1; - fprintf(ctx->out, "%s\n", l->line); - debug3("%s: %s key already at %s:%ld", __func__, - sshkey_type(l->key), l->path, l->linenum); - return 0; - } + if (!sshkey_equal(ctx->keys[i], l->key)) + continue; + ctx->match_keys[i] |= l->match; + fprintf(ctx->out, "%s\n", l->line); + debug3_f("%s key already at %s:%ld", + sshkey_type(l->key), l->path, l->linenum); + return 0; } /* @@ -528,15 +590,19 @@ hostfile_replace_entries(const char *filename, const char *host, const char *ip, int loglevel = quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_VERBOSE; struct host_delete_ctx ctx; char *fp, *temp = NULL, *back = NULL; + const char *what; mode_t omask; size_t i; + u_int want; omask = umask(077); memset(&ctx, 0, sizeof(ctx)); ctx.host = host; + ctx.ip = ip; ctx.quiet = quiet; - if ((ctx.skip_keys = calloc(nkeys, sizeof(*ctx.skip_keys))) == NULL) + + if ((ctx.match_keys = calloc(nkeys, sizeof(*ctx.match_keys))) == NULL) return SSH_ERR_ALLOC_FAIL; ctx.keys = keys; ctx.nkeys = nkeys; @@ -545,50 +611,73 @@ hostfile_replace_entries(const char *filename, const char *host, const char *ip, /* * Prepare temporary file for in-place deletion. */ - if ((r = asprintf(&temp, "%s.XXXXXXXXXXX", filename)) < 0 || - (r = asprintf(&back, "%s.old", filename)) < 0) { + if ((r = asprintf(&temp, "%s.XXXXXXXXXXX", filename)) == -1 || + (r = asprintf(&back, "%s.old", filename)) == -1) { r = SSH_ERR_ALLOC_FAIL; goto fail; } if ((fd = mkstemp(temp)) == -1) { oerrno = errno; - error("%s: mkstemp: %s", __func__, strerror(oerrno)); + error_f("mkstemp: %s", strerror(oerrno)); r = SSH_ERR_SYSTEM_ERROR; goto fail; } if ((ctx.out = fdopen(fd, "w")) == NULL) { oerrno = errno; close(fd); - error("%s: fdopen: %s", __func__, strerror(oerrno)); + error_f("fdopen: %s", strerror(oerrno)); r = SSH_ERR_SYSTEM_ERROR; goto fail; } - /* Remove all entries for the specified host from the file */ + /* Remove stale/mismatching entries for the specified host */ if ((r = hostkeys_foreach(filename, host_delete, &ctx, host, ip, - HKF_WANT_PARSE_KEY)) != 0) { - error("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r)); + HKF_WANT_PARSE_KEY, 0)) != 0) { + oerrno = errno; + error_fr(r, "hostkeys_foreach"); goto fail; } - /* Add the requested keys */ + /* Re-add the requested keys */ + want = HKF_MATCH_HOST | (ip == NULL ? 0 : HKF_MATCH_IP); for (i = 0; i < nkeys; i++) { - if (ctx.skip_keys[i]) + if ((want & ctx.match_keys[i]) == want) continue; if ((fp = sshkey_fingerprint(keys[i], hash_alg, SSH_FP_DEFAULT)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto fail; } - do_log2(loglevel, "%s%sAdding new key for %s to %s: %s %s", - quiet ? __func__ : "", quiet ? ": " : "", host, filename, + /* write host/ip */ + what = ""; + if (ctx.match_keys[i] == 0) { + what = "Adding new key"; + if (!write_host_entry(ctx.out, host, ip, + keys[i], store_hash)) { + r = SSH_ERR_INTERNAL_ERROR; + goto fail; + } + } else if ((want & ~ctx.match_keys[i]) == HKF_MATCH_HOST) { + what = "Fixing match (hostname)"; + if (!write_host_entry(ctx.out, host, NULL, + keys[i], store_hash)) { + r = SSH_ERR_INTERNAL_ERROR; + goto fail; + } + } else if ((want & ~ctx.match_keys[i]) == HKF_MATCH_IP) { + what = "Fixing match (address)"; + if (!write_host_entry(ctx.out, ip, NULL, + keys[i], store_hash)) { + r = SSH_ERR_INTERNAL_ERROR; + goto fail; + } + } + do_log2(loglevel, "%s%s%s for %s%s%s to %s: %s %s", + quiet ? __func__ : "", quiet ? ": " : "", what, + host, ip == NULL ? "" : ",", ip == NULL ? "" : ip, filename, sshkey_ssh_name(keys[i]), fp); free(fp); - if (!write_host_entry(ctx.out, host, ip, keys[i], store_hash)) { - r = SSH_ERR_INTERNAL_ERROR; - goto fail; - } ctx.modified = 1; } fclose(ctx.out); @@ -598,30 +687,28 @@ hostfile_replace_entries(const char *filename, const char *host, const char *ip, /* Backup the original file and replace it with the temporary */ if (unlink(back) == -1 && errno != ENOENT) { oerrno = errno; - error("%s: unlink %.100s: %s", __func__, - back, strerror(errno)); + error_f("unlink %.100s: %s", back, strerror(errno)); r = SSH_ERR_SYSTEM_ERROR; goto fail; } if (link(filename, back) == -1) { oerrno = errno; - error("%s: link %.100s to %.100s: %s", __func__, - filename, back, strerror(errno)); + error_f("link %.100s to %.100s: %s", filename, + back, strerror(errno)); r = SSH_ERR_SYSTEM_ERROR; goto fail; } if (rename(temp, filename) == -1) { oerrno = errno; - error("%s: rename \"%s\" to \"%s\": %s", __func__, - temp, filename, strerror(errno)); + error_f("rename \"%s\" to \"%s\": %s", temp, + filename, strerror(errno)); r = SSH_ERR_SYSTEM_ERROR; goto fail; } } else { /* No changes made; just delete the temporary file */ if (unlink(temp) != 0) - error("%s: unlink \"%s\": %s", __func__, - temp, strerror(errno)); + error_f("unlink \"%s\": %s", temp, strerror(errno)); } /* success */ @@ -633,7 +720,7 @@ hostfile_replace_entries(const char *filename, const char *host, const char *ip, free(back); if (ctx.out != NULL) fclose(ctx.out); - free(ctx.skip_keys); + free(ctx.match_keys); umask(omask); if (r == SSH_ERR_SYSTEM_ERROR) errno = oerrno; @@ -659,10 +746,9 @@ match_maybe_hashed(const char *host, const char *names, int *was_hashed) } int -hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, - const char *host, const char *ip, u_int options) +hostkeys_foreach_file(const char *path, FILE *f, hostkeys_foreach_fn *callback, + void *ctx, const char *host, const char *ip, u_int options, u_int note) { - FILE *f; char *line = NULL, ktype[128]; u_long linenum = 0; char *cp, *cp2; @@ -675,10 +761,7 @@ hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, memset(&lineinfo, 0, sizeof(lineinfo)); if (host == NULL && (options & HKF_WANT_MATCH) != 0) return SSH_ERR_INVALID_ARGUMENT; - if ((f = fopen(path, "r")) == NULL) - return SSH_ERR_SYSTEM_ERROR; - debug3("%s: reading file \"%s\"", __func__, path); while (getline(&line, &linesize, f) != -1) { linenum++; line[strcspn(line, "\n")] = '\0'; @@ -692,6 +775,7 @@ hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, lineinfo.marker = MRK_NONE; lineinfo.status = HKF_STATUS_OK; lineinfo.keytype = KEY_UNSPEC; + lineinfo.note = note; /* Skip any leading whitespace, comments and empty lines. */ for (cp = line; *cp == ' ' || *cp == '\t'; cp++) @@ -706,8 +790,7 @@ hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, } if ((lineinfo.marker = check_markers(&cp)) == MRK_ERROR) { - verbose("%s: invalid marker at %s:%lu", - __func__, path, linenum); + verbose_f("invalid marker at %s:%lu", path, linenum); if ((options & HKF_WANT_MATCH) == 0) goto bad; continue; @@ -723,8 +806,8 @@ hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, if (host != NULL) { if ((s = match_maybe_hashed(host, lineinfo.hosts, &hashed)) == -1) { - debug2("%s: %s:%ld: bad host hash \"%.32s\"", - __func__, path, linenum, lineinfo.hosts); + debug2_f("%s:%ld: bad host hash \"%.32s\"", + path, linenum, lineinfo.hosts); goto bad; } if (s == 1) { @@ -736,9 +819,9 @@ hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, if (ip != NULL) { if ((s = match_maybe_hashed(ip, lineinfo.hosts, &hashed)) == -1) { - debug2("%s: %s:%ld: bad ip hash " - "\"%.32s\"", __func__, path, - linenum, lineinfo.hosts); + debug2_f("%s:%ld: bad ip hash " + "\"%.32s\"", path, linenum, + lineinfo.hosts); goto bad; } if (s == 1) { @@ -773,7 +856,7 @@ hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, * lines. */ if ((lineinfo.key = sshkey_new(KEY_UNSPEC)) == NULL) { - error("%s: sshkey_new failed", __func__); + error_f("sshkey_new failed"); r = SSH_ERR_ALLOC_FAIL; break; } @@ -829,6 +912,24 @@ hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, sshkey_free(lineinfo.key); free(lineinfo.line); free(line); + return r; +} + +int +hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, + const char *host, const char *ip, u_int options, u_int note) +{ + FILE *f; + int r, oerrno; + + if ((f = fopen(path, "r")) == NULL) + return SSH_ERR_SYSTEM_ERROR; + + debug3_f("reading file \"%s\"", path); + r = hostkeys_foreach_file(path, f, callback, ctx, host, ip, + options, note); + oerrno = errno; fclose(f); + errno = oerrno; return r; } |