upstream commit
Host key rotation support. Add a hostkeys@openssh.com protocol extension (global request) for a server to inform a client of all its available host key after authentication has completed. The client may record the keys in known_hosts, allowing it to upgrade to better host key algorithms and a server to gracefully rotate its keys. The client side of this is controlled by a UpdateHostkeys config option (default on). ok markus@
This commit is contained in:
parent
60b1825262
commit
8d4f87258f
24
PROTOCOL
24
PROTOCOL
|
@ -282,6 +282,28 @@ by the client cancel the forwarding of a Unix domain socket.
|
||||||
boolean FALSE
|
boolean FALSE
|
||||||
string socket path
|
string socket path
|
||||||
|
|
||||||
|
2.5. connection: hostkey update and rotation "hostkeys@openssh.com"
|
||||||
|
|
||||||
|
OpenSSH supports a protocol extension allowing a server to inform
|
||||||
|
a client of all its protocol v.2 hostkeys after user-authentication
|
||||||
|
has completed.
|
||||||
|
|
||||||
|
byte SSH_MSG_GLOBAL_REQUEST
|
||||||
|
string "hostkeys@openssh.com"
|
||||||
|
string[] hostkeys
|
||||||
|
|
||||||
|
Upon receiving this message, a client may update its known_hosts
|
||||||
|
file, adding keys that it has not seen before and deleting keys
|
||||||
|
for the server host that are no longer offered.
|
||||||
|
|
||||||
|
This extension allows a client to learn key types that it had
|
||||||
|
not previously encountered, thereby allowing it to potentially
|
||||||
|
upgrade from weaker key algorithms to better ones. It also
|
||||||
|
supports graceful key rotation: a server may offer multiple keys
|
||||||
|
of the same type for a period (to give clients an opportunity to
|
||||||
|
learn them using this extension) before removing the deprecated
|
||||||
|
key from those offered.
|
||||||
|
|
||||||
3. SFTP protocol changes
|
3. SFTP protocol changes
|
||||||
|
|
||||||
3.1. sftp: Reversal of arguments to SSH_FXP_SYMLINK
|
3.1. sftp: Reversal of arguments to SSH_FXP_SYMLINK
|
||||||
|
@ -406,4 +428,4 @@ respond with a SSH_FXP_STATUS message.
|
||||||
This extension is advertised in the SSH_FXP_VERSION hello with version
|
This extension is advertised in the SSH_FXP_VERSION hello with version
|
||||||
"1".
|
"1".
|
||||||
|
|
||||||
$OpenBSD: PROTOCOL,v 1.24 2014/07/15 15:54:14 millert Exp $
|
$OpenBSD: PROTOCOL,v 1.25 2015/01/26 03:04:45 djm Exp $
|
||||||
|
|
94
clientloop.c
94
clientloop.c
|
@ -1,4 +1,4 @@
|
||||||
/* $OpenBSD: clientloop.c,v 1.266 2015/01/20 23:14:00 deraadt Exp $ */
|
/* $OpenBSD: clientloop.c,v 1.267 2015/01/26 03:04:45 djm Exp $ */
|
||||||
/*
|
/*
|
||||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||||
|
@ -112,6 +112,7 @@
|
||||||
#include "msg.h"
|
#include "msg.h"
|
||||||
#include "roaming.h"
|
#include "roaming.h"
|
||||||
#include "ssherr.h"
|
#include "ssherr.h"
|
||||||
|
#include "hostfile.h"
|
||||||
|
|
||||||
/* import options */
|
/* import options */
|
||||||
extern Options options;
|
extern Options options;
|
||||||
|
@ -1781,6 +1782,7 @@ client_input_exit_status(int type, u_int32_t seq, void *ctxt)
|
||||||
quit_pending = 1;
|
quit_pending = 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
client_input_agent_open(int type, u_int32_t seq, void *ctxt)
|
client_input_agent_open(int type, u_int32_t seq, void *ctxt)
|
||||||
{
|
{
|
||||||
|
@ -2038,6 +2040,7 @@ client_input_channel_open(int type, u_int32_t seq, void *ctxt)
|
||||||
free(ctype);
|
free(ctype);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
client_input_channel_req(int type, u_int32_t seq, void *ctxt)
|
client_input_channel_req(int type, u_int32_t seq, void *ctxt)
|
||||||
{
|
{
|
||||||
|
@ -2085,6 +2088,91 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt)
|
||||||
free(rtype);
|
free(rtype);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handle hostkeys@openssh.com global request to inform the client of all
|
||||||
|
* the server's hostkeys. The keys are checked against the user's
|
||||||
|
* HostkeyAlgorithms preference before they are accepted.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
client_input_hostkeys(void)
|
||||||
|
{
|
||||||
|
const u_char *blob = NULL;
|
||||||
|
u_int i, len = 0, nkeys = 0;
|
||||||
|
struct sshbuf *buf = NULL;
|
||||||
|
struct sshkey *key = NULL, **tmp, **keys = NULL;
|
||||||
|
int r, success = 1;
|
||||||
|
char *fp, *host_str = NULL;
|
||||||
|
static int hostkeys_seen = 0; /* XXX use struct ssh */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NB. Return success for all cases other than protocol error. The
|
||||||
|
* server doesn't need to know what the client does with its hosts
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
blob = packet_get_string_ptr(&len);
|
||||||
|
packet_check_eom();
|
||||||
|
|
||||||
|
if (hostkeys_seen)
|
||||||
|
fatal("%s: server already sent hostkeys", __func__);
|
||||||
|
if (!options.update_hostkeys || options.num_user_hostfiles <= 0)
|
||||||
|
return 1;
|
||||||
|
if ((buf = sshbuf_from(blob, len)) == NULL)
|
||||||
|
fatal("%s: sshbuf_from failed", __func__);
|
||||||
|
while (sshbuf_len(buf) > 0) {
|
||||||
|
sshkey_free(key);
|
||||||
|
key = NULL;
|
||||||
|
if ((r = sshkey_froms(buf, &key)) != 0)
|
||||||
|
fatal("%s: parse key: %s", __func__, ssh_err(r));
|
||||||
|
fp = sshkey_fingerprint(key, options.fingerprint_hash,
|
||||||
|
SSH_FP_DEFAULT);
|
||||||
|
debug3("%s: received %s key %s", __func__,
|
||||||
|
sshkey_type(key), fp);
|
||||||
|
free(fp);
|
||||||
|
/* Check that the key is accepted in HostkeyAlgorithms */
|
||||||
|
if (options.hostkeyalgorithms != NULL &&
|
||||||
|
match_pattern_list(sshkey_ssh_name(key),
|
||||||
|
options.hostkeyalgorithms,
|
||||||
|
strlen(options.hostkeyalgorithms), 0) != 1) {
|
||||||
|
debug3("%s: %s key not permitted by HostkeyAlgorithms",
|
||||||
|
__func__, sshkey_ssh_name(key));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((tmp = reallocarray(keys, nkeys + 1,
|
||||||
|
sizeof(*keys))) == NULL)
|
||||||
|
fatal("%s: reallocarray failed nkeys = %u",
|
||||||
|
__func__, nkeys);
|
||||||
|
keys = tmp;
|
||||||
|
keys[nkeys++] = key;
|
||||||
|
key = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug3("%s: received %u keys from server", __func__, nkeys);
|
||||||
|
if (nkeys == 0) {
|
||||||
|
error("%s: server sent no hostkeys", __func__);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_hostfile_hostname_ipaddr(host, NULL, options.port, &host_str, NULL);
|
||||||
|
|
||||||
|
if ((r = hostfile_replace_entries(options.user_hostfiles[0], host_str,
|
||||||
|
keys, nkeys, options.hash_known_hosts, 1)) != 0) {
|
||||||
|
error("%s: hostfile_replace_entries failed: %s",
|
||||||
|
__func__, ssh_err(r));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Success */
|
||||||
|
out:
|
||||||
|
free(host_str);
|
||||||
|
sshkey_free(key);
|
||||||
|
for (i = 0; i < nkeys; i++)
|
||||||
|
sshkey_free(keys[i]);
|
||||||
|
sshbuf_free(buf);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
client_input_global_request(int type, u_int32_t seq, void *ctxt)
|
client_input_global_request(int type, u_int32_t seq, void *ctxt)
|
||||||
{
|
{
|
||||||
|
@ -2092,10 +2180,12 @@ client_input_global_request(int type, u_int32_t seq, void *ctxt)
|
||||||
int want_reply;
|
int want_reply;
|
||||||
int success = 0;
|
int success = 0;
|
||||||
|
|
||||||
rtype = packet_get_string(NULL);
|
rtype = packet_get_cstring(NULL);
|
||||||
want_reply = packet_get_char();
|
want_reply = packet_get_char();
|
||||||
debug("client_input_global_request: rtype %s want_reply %d",
|
debug("client_input_global_request: rtype %s want_reply %d",
|
||||||
rtype, want_reply);
|
rtype, want_reply);
|
||||||
|
if (strcmp(rtype, "hostkeys@openssh.com") == 0)
|
||||||
|
success = client_input_hostkeys();
|
||||||
if (want_reply) {
|
if (want_reply) {
|
||||||
packet_start(success ?
|
packet_start(success ?
|
||||||
SSH2_MSG_REQUEST_SUCCESS : SSH2_MSG_REQUEST_FAILURE);
|
SSH2_MSG_REQUEST_SUCCESS : SSH2_MSG_REQUEST_FAILURE);
|
||||||
|
|
206
hostfile.c
206
hostfile.c
|
@ -1,4 +1,4 @@
|
||||||
/* $OpenBSD: hostfile.c,v 1.61 2015/01/18 21:48:09 djm Exp $ */
|
/* $OpenBSD: hostfile.c,v 1.62 2015/01/26 03:04:45 djm Exp $ */
|
||||||
/*
|
/*
|
||||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||||
|
@ -39,6 +39,7 @@
|
||||||
#include "includes.h"
|
#include "includes.h"
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
|
|
||||||
|
@ -49,6 +50,7 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "xmalloc.h"
|
#include "xmalloc.h"
|
||||||
#include "match.h"
|
#include "match.h"
|
||||||
|
@ -430,6 +432,29 @@ lookup_key_in_hostkeys_by_type(struct hostkeys *hostkeys, int keytype,
|
||||||
found) == HOST_FOUND);
|
found) == HOST_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
write_host_entry(FILE *f, const char *host,
|
||||||
|
const struct sshkey *key, int store_hash)
|
||||||
|
{
|
||||||
|
int r, success = 0;
|
||||||
|
char *hashed_host = NULL;
|
||||||
|
|
||||||
|
if (store_hash) {
|
||||||
|
if ((hashed_host = host_hash(host, NULL, 0)) == NULL) {
|
||||||
|
error("%s: host_hash failed", __func__);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(f, "%s ", store_hash ? hashed_host : host);
|
||||||
|
|
||||||
|
if ((r = sshkey_write(key, f)) == 0)
|
||||||
|
success = 1;
|
||||||
|
else
|
||||||
|
error("%s: sshkey_write failed: %s", __func__, ssh_err(r));
|
||||||
|
fputc('\n', f);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Appends an entry to the host file. Returns false if the entry could not
|
* Appends an entry to the host file. Returns false if the entry could not
|
||||||
* be appended.
|
* be appended.
|
||||||
|
@ -439,32 +464,181 @@ add_host_to_hostfile(const char *filename, const char *host,
|
||||||
const struct sshkey *key, int store_hash)
|
const struct sshkey *key, int store_hash)
|
||||||
{
|
{
|
||||||
FILE *f;
|
FILE *f;
|
||||||
int r, success = 0;
|
int success;
|
||||||
char *hashed_host = NULL;
|
|
||||||
|
|
||||||
if (key == NULL)
|
if (key == NULL)
|
||||||
return 1; /* XXX ? */
|
return 1; /* XXX ? */
|
||||||
f = fopen(filename, "a");
|
f = fopen(filename, "a");
|
||||||
if (!f)
|
if (!f)
|
||||||
return 0;
|
return 0;
|
||||||
|
success = write_host_entry(f, host, key, store_hash);
|
||||||
if (store_hash) {
|
|
||||||
if ((hashed_host = host_hash(host, NULL, 0)) == NULL) {
|
|
||||||
error("%s: host_hash failed", __func__);
|
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct host_delete_ctx {
|
||||||
|
FILE *out;
|
||||||
|
int quiet;
|
||||||
|
const char *host;
|
||||||
|
int *skip_keys;
|
||||||
|
struct sshkey * const *keys;
|
||||||
|
size_t nkeys;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
host_delete(struct hostkey_foreach_line *l, void *_ctx)
|
||||||
|
{
|
||||||
|
struct host_delete_ctx *ctx = (struct host_delete_ctx *)_ctx;
|
||||||
|
int loglevel = ctx->quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (l->status == HKF_STATUS_HOST_MATCHED) {
|
||||||
|
if (l->marker != MRK_NONE) {
|
||||||
|
/* Don't remove CA and revocation lines */
|
||||||
|
fprintf(ctx->out, "%s\n", l->line);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* XXX might need a knob for this later */
|
||||||
|
/* Don't remove RSA1 keys */
|
||||||
|
if (l->key->type == KEY_RSA1) {
|
||||||
|
fprintf(ctx->out, "%s\n", l->line);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fprintf(f, "%s ", store_hash ? hashed_host : host);
|
|
||||||
|
|
||||||
if ((r = sshkey_write(key, f)) != 0) {
|
/*
|
||||||
error("%s: saving key in %s failed: %s",
|
* Hostname matches and has no CA/revoke marker, delete it
|
||||||
__func__, filename, ssh_err(r));
|
* by *not* writing the line to ctx->out.
|
||||||
} else
|
*/
|
||||||
success = 1;
|
do_log2(loglevel, "%s%s%s:%ld: Host %s removed",
|
||||||
fputc('\n', f);
|
ctx->quiet ? __func__ : "", ctx->quiet ? ": " : "",
|
||||||
fclose(f);
|
l->path, l->linenum, ctx->host);
|
||||||
return success;
|
return 0;
|
||||||
|
}
|
||||||
|
/* Retain non-matching hosts and invalid lines when deleting */
|
||||||
|
if (l->status == HKF_STATUS_INVALID) {
|
||||||
|
do_log2(loglevel, "%s%s%s:%ld: invalid known_hosts entry",
|
||||||
|
ctx->quiet ? __func__ : "", ctx->quiet ? ": " : "",
|
||||||
|
l->path, l->linenum);
|
||||||
|
}
|
||||||
|
fprintf(ctx->out, "%s\n", l->line);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
hostfile_replace_entries(const char *filename, const char *host,
|
||||||
|
struct sshkey **keys, size_t nkeys, int store_hash, int quiet)
|
||||||
|
{
|
||||||
|
int r, fd, oerrno = 0;
|
||||||
|
int loglevel = quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO;
|
||||||
|
struct host_delete_ctx ctx;
|
||||||
|
char *temp = NULL, *back = NULL;
|
||||||
|
mode_t omask;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
memset(&ctx, 0, sizeof(ctx));
|
||||||
|
ctx.host = host;
|
||||||
|
ctx.quiet = quiet;
|
||||||
|
if ((ctx.skip_keys = calloc(nkeys, sizeof(*ctx.skip_keys))) == NULL)
|
||||||
|
return SSH_ERR_ALLOC_FAIL;
|
||||||
|
ctx.keys = keys;
|
||||||
|
ctx.nkeys = nkeys;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prepare temporary file for in-place deletion.
|
||||||
|
*/
|
||||||
|
if ((r = asprintf(&temp, "%s.XXXXXXXXXXX", filename)) < 0 ||
|
||||||
|
(r = asprintf(&back, "%s.old", filename)) < 0) {
|
||||||
|
r = SSH_ERR_ALLOC_FAIL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
omask = umask(077);
|
||||||
|
if ((fd = mkstemp(temp)) == -1) {
|
||||||
|
oerrno = errno;
|
||||||
|
error("%s: mkstemp: %s", __func__, 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));
|
||||||
|
r = SSH_ERR_SYSTEM_ERROR;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove all entries for the specified host from the file */
|
||||||
|
if ((r = hostkeys_foreach(filename, host_delete, &ctx, host,
|
||||||
|
HKF_WANT_PARSE_KEY)) != 0) {
|
||||||
|
error("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r));
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add the requested keys */
|
||||||
|
for (i = 0; i < nkeys; i++) {
|
||||||
|
if (ctx.skip_keys[i])
|
||||||
|
continue;
|
||||||
|
do_log2(loglevel, "%s%sadd %s key to %s",
|
||||||
|
quiet ? __func__ : "", quiet ? ": " : NULL,
|
||||||
|
sshkey_type(keys[i]), filename);
|
||||||
|
if (!write_host_entry(ctx.out, host, keys[i], store_hash)) {
|
||||||
|
r = SSH_ERR_INTERNAL_ERROR;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose(ctx.out);
|
||||||
|
ctx.out = NULL;
|
||||||
|
|
||||||
|
/* 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));
|
||||||
|
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));
|
||||||
|
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));
|
||||||
|
r = SSH_ERR_SYSTEM_ERROR;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
/* success */
|
||||||
|
r = 0;
|
||||||
|
fail:
|
||||||
|
if (temp != NULL && r != 0)
|
||||||
|
unlink(temp);
|
||||||
|
free(temp);
|
||||||
|
free(back);
|
||||||
|
if (ctx.out != NULL)
|
||||||
|
fclose(ctx.out);
|
||||||
|
free(ctx.skip_keys);
|
||||||
|
if (r == SSH_ERR_SYSTEM_ERROR)
|
||||||
|
errno = oerrno;
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* $OpenBSD: hostfile.h,v 1.22 2015/01/18 21:40:24 djm Exp $ */
|
/* $OpenBSD: hostfile.h,v 1.23 2015/01/26 03:04:45 djm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||||
|
@ -44,6 +44,9 @@ int hostfile_read_key(char **, u_int *, struct sshkey *);
|
||||||
int add_host_to_hostfile(const char *, const char *,
|
int add_host_to_hostfile(const char *, const char *,
|
||||||
const struct sshkey *, int);
|
const struct sshkey *, int);
|
||||||
|
|
||||||
|
int hostfile_replace_entries(const char *filename, const char *host,
|
||||||
|
struct sshkey **keys, size_t nkeys, int store_hash, int quiet);
|
||||||
|
|
||||||
#define HASH_MAGIC "|1|"
|
#define HASH_MAGIC "|1|"
|
||||||
#define HASH_DELIM '|'
|
#define HASH_DELIM '|'
|
||||||
|
|
||||||
|
|
13
readconf.c
13
readconf.c
|
@ -1,4 +1,4 @@
|
||||||
/* $OpenBSD: readconf.c,v 1.228 2015/01/16 06:40:12 deraadt Exp $ */
|
/* $OpenBSD: readconf.c,v 1.229 2015/01/26 03:04:45 djm Exp $ */
|
||||||
/*
|
/*
|
||||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||||
|
@ -156,7 +156,7 @@ typedef enum {
|
||||||
oCanonicalDomains, oCanonicalizeHostname, oCanonicalizeMaxDots,
|
oCanonicalDomains, oCanonicalizeHostname, oCanonicalizeMaxDots,
|
||||||
oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs,
|
oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs,
|
||||||
oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys,
|
oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys,
|
||||||
oFingerprintHash,
|
oFingerprintHash, oUpdateHostkeys,
|
||||||
oIgnoredUnknownOption, oDeprecated, oUnsupported
|
oIgnoredUnknownOption, oDeprecated, oUnsupported
|
||||||
} OpCodes;
|
} OpCodes;
|
||||||
|
|
||||||
|
@ -273,6 +273,7 @@ static struct {
|
||||||
{ "streamlocalbindunlink", oStreamLocalBindUnlink },
|
{ "streamlocalbindunlink", oStreamLocalBindUnlink },
|
||||||
{ "revokedhostkeys", oRevokedHostKeys },
|
{ "revokedhostkeys", oRevokedHostKeys },
|
||||||
{ "fingerprinthash", oFingerprintHash },
|
{ "fingerprinthash", oFingerprintHash },
|
||||||
|
{ "updatehostkeys", oUpdateHostkeys },
|
||||||
{ "ignoreunknown", oIgnoreUnknown },
|
{ "ignoreunknown", oIgnoreUnknown },
|
||||||
|
|
||||||
{ NULL, oBadOption }
|
{ NULL, oBadOption }
|
||||||
|
@ -1476,6 +1477,10 @@ parse_int:
|
||||||
*intptr = value;
|
*intptr = value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case oUpdateHostkeys:
|
||||||
|
intptr = &options->update_hostkeys;
|
||||||
|
goto parse_flag;
|
||||||
|
|
||||||
case oDeprecated:
|
case oDeprecated:
|
||||||
debug("%s line %d: Deprecated option \"%s\"",
|
debug("%s line %d: Deprecated option \"%s\"",
|
||||||
filename, linenum, keyword);
|
filename, linenum, keyword);
|
||||||
|
@ -1654,6 +1659,7 @@ initialize_options(Options * options)
|
||||||
options->canonicalize_hostname = -1;
|
options->canonicalize_hostname = -1;
|
||||||
options->revoked_host_keys = NULL;
|
options->revoked_host_keys = NULL;
|
||||||
options->fingerprint_hash = -1;
|
options->fingerprint_hash = -1;
|
||||||
|
options->update_hostkeys = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1833,6 +1839,8 @@ fill_default_options(Options * options)
|
||||||
options->canonicalize_hostname = SSH_CANONICALISE_NO;
|
options->canonicalize_hostname = SSH_CANONICALISE_NO;
|
||||||
if (options->fingerprint_hash == -1)
|
if (options->fingerprint_hash == -1)
|
||||||
options->fingerprint_hash = SSH_FP_HASH_DEFAULT;
|
options->fingerprint_hash = SSH_FP_HASH_DEFAULT;
|
||||||
|
if (options->update_hostkeys == -1)
|
||||||
|
options->update_hostkeys = 1;
|
||||||
|
|
||||||
#define CLEAR_ON_NONE(v) \
|
#define CLEAR_ON_NONE(v) \
|
||||||
do { \
|
do { \
|
||||||
|
@ -2256,6 +2264,7 @@ dump_client_config(Options *o, const char *host)
|
||||||
dump_cfg_fmtint(oUsePrivilegedPort, o->use_privileged_port);
|
dump_cfg_fmtint(oUsePrivilegedPort, o->use_privileged_port);
|
||||||
dump_cfg_fmtint(oVerifyHostKeyDNS, o->verify_host_key_dns);
|
dump_cfg_fmtint(oVerifyHostKeyDNS, o->verify_host_key_dns);
|
||||||
dump_cfg_fmtint(oVisualHostKey, o->visual_host_key);
|
dump_cfg_fmtint(oVisualHostKey, o->visual_host_key);
|
||||||
|
dump_cfg_fmtint(oUpdateHostkeys, o->update_hostkeys);
|
||||||
|
|
||||||
/* Integer options */
|
/* Integer options */
|
||||||
dump_cfg_int(oCanonicalizeMaxDots, o->canonicalize_max_dots);
|
dump_cfg_int(oCanonicalizeMaxDots, o->canonicalize_max_dots);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* $OpenBSD: readconf.h,v 1.106 2015/01/15 09:40:00 djm Exp $ */
|
/* $OpenBSD: readconf.h,v 1.107 2015/01/26 03:04:45 djm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||||
|
@ -148,6 +148,8 @@ typedef struct {
|
||||||
|
|
||||||
int fingerprint_hash;
|
int fingerprint_hash;
|
||||||
|
|
||||||
|
int update_hostkeys;
|
||||||
|
|
||||||
char *ignored_unknown; /* Pattern list of unknown tokens to ignore */
|
char *ignored_unknown; /* Pattern list of unknown tokens to ignore */
|
||||||
} Options;
|
} Options;
|
||||||
|
|
||||||
|
|
26
ssh_config.5
26
ssh_config.5
|
@ -33,8 +33,8 @@
|
||||||
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
.\"
|
.\"
|
||||||
.\" $OpenBSD: ssh_config.5,v 1.199 2014/12/22 09:24:59 jmc Exp $
|
.\" $OpenBSD: ssh_config.5,v 1.200 2015/01/26 03:04:45 djm Exp $
|
||||||
.Dd $Mdocdate: December 22 2014 $
|
.Dd $Mdocdate: January 26 2015 $
|
||||||
.Dt SSH_CONFIG 5
|
.Dt SSH_CONFIG 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
@ -1492,6 +1492,28 @@ is not specified, it defaults to
|
||||||
.Dq any .
|
.Dq any .
|
||||||
The default is
|
The default is
|
||||||
.Dq any:any .
|
.Dq any:any .
|
||||||
|
.It Cm UpdateHostkeys
|
||||||
|
Specifies whether
|
||||||
|
.Xr ssh 1
|
||||||
|
should accept notifications of additional hostkeys from the server sent
|
||||||
|
after authentication has completed and add them to
|
||||||
|
.Cm UserKnownHostsFile .
|
||||||
|
The argument must be
|
||||||
|
.Dq yes
|
||||||
|
(the default)
|
||||||
|
or
|
||||||
|
.Dq no .
|
||||||
|
Enabling this option allows learning alternate hostkeys for a server
|
||||||
|
and supports graceful key rotation by allowing a server to public replacement
|
||||||
|
keys before old ones are removed.
|
||||||
|
Additional hostkeys are only accepted if the key used to authenticate the
|
||||||
|
host was already trusted or explicity accepted by the user.
|
||||||
|
.Pp
|
||||||
|
Presently, only
|
||||||
|
.Xr sshd 8
|
||||||
|
from OpenSSH 6.8 and greater support the
|
||||||
|
.Dq hostkeys@openssh.com
|
||||||
|
protocol extension used to inform the client of all the server's hostkeys.
|
||||||
.It Cm UsePrivilegedPort
|
.It Cm UsePrivilegedPort
|
||||||
Specifies whether to use a privileged port for outgoing connections.
|
Specifies whether to use a privileged port for outgoing connections.
|
||||||
The argument must be
|
The argument must be
|
||||||
|
|
11
sshconnect.c
11
sshconnect.c
|
@ -1,4 +1,4 @@
|
||||||
/* $OpenBSD: sshconnect.c,v 1.256 2015/01/20 23:14:00 deraadt Exp $ */
|
/* $OpenBSD: sshconnect.c,v 1.257 2015/01/26 03:04:46 djm Exp $ */
|
||||||
/*
|
/*
|
||||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||||
|
@ -818,6 +818,7 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
|
||||||
int len, cancelled_forwarding = 0;
|
int len, cancelled_forwarding = 0;
|
||||||
int local = sockaddr_is_local(hostaddr);
|
int local = sockaddr_is_local(hostaddr);
|
||||||
int r, want_cert = key_is_cert(host_key), host_ip_differ = 0;
|
int r, want_cert = key_is_cert(host_key), host_ip_differ = 0;
|
||||||
|
int hostkey_trusted = 0; /* Known or explicitly accepted by user */
|
||||||
struct hostkeys *host_hostkeys, *ip_hostkeys;
|
struct hostkeys *host_hostkeys, *ip_hostkeys;
|
||||||
u_int i;
|
u_int i;
|
||||||
|
|
||||||
|
@ -926,6 +927,7 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
|
||||||
free(ra);
|
free(ra);
|
||||||
free(fp);
|
free(fp);
|
||||||
}
|
}
|
||||||
|
hostkey_trusted = 1;
|
||||||
break;
|
break;
|
||||||
case HOST_NEW:
|
case HOST_NEW:
|
||||||
if (options.host_key_alias == NULL && port != 0 &&
|
if (options.host_key_alias == NULL && port != 0 &&
|
||||||
|
@ -989,6 +991,7 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
|
||||||
free(fp);
|
free(fp);
|
||||||
if (!confirm(msg))
|
if (!confirm(msg))
|
||||||
goto fail;
|
goto fail;
|
||||||
|
hostkey_trusted = 1; /* user explicitly confirmed */
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* If not in strict mode, add the key automatically to the
|
* If not in strict mode, add the key automatically to the
|
||||||
|
@ -1187,6 +1190,12 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hostkey_trusted && options.update_hostkeys) {
|
||||||
|
debug("%s: hostkey not known or explicitly trusted: "
|
||||||
|
"disabling UpdateHostkeys", __func__);
|
||||||
|
options.update_hostkeys = 0;
|
||||||
|
}
|
||||||
|
|
||||||
free(ip);
|
free(ip);
|
||||||
free(host);
|
free(host);
|
||||||
if (host_hostkeys != NULL)
|
if (host_hostkeys != NULL)
|
||||||
|
|
44
sshd.c
44
sshd.c
|
@ -1,4 +1,4 @@
|
||||||
/* $OpenBSD: sshd.c,v 1.438 2015/01/20 23:14:00 deraadt Exp $ */
|
/* $OpenBSD: sshd.c,v 1.439 2015/01/26 03:04:46 djm Exp $ */
|
||||||
/*
|
/*
|
||||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||||
|
@ -911,6 +911,42 @@ get_hostkey_index(Key *key, struct ssh *ssh)
|
||||||
return (-1);
|
return (-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Inform the client of all hostkeys */
|
||||||
|
static void
|
||||||
|
notify_hostkeys(struct ssh *ssh)
|
||||||
|
{
|
||||||
|
struct sshbuf *buf;
|
||||||
|
struct sshkey *key;
|
||||||
|
int i, nkeys, r;
|
||||||
|
char *fp;
|
||||||
|
|
||||||
|
if ((buf = sshbuf_new()) == NULL)
|
||||||
|
fatal("%s: sshbuf_new", __func__);
|
||||||
|
for (i = nkeys = 0; i < options.num_host_key_files; i++) {
|
||||||
|
key = get_hostkey_public_by_index(i, ssh);
|
||||||
|
if (key == NULL || key->type == KEY_UNSPEC ||
|
||||||
|
key->type == KEY_RSA1 || sshkey_is_cert(key))
|
||||||
|
continue;
|
||||||
|
fp = sshkey_fingerprint(key, options.fingerprint_hash,
|
||||||
|
SSH_FP_DEFAULT);
|
||||||
|
debug3("%s: key %d: %s %s", __func__, i,
|
||||||
|
sshkey_ssh_name(key), fp);
|
||||||
|
free(fp);
|
||||||
|
if ((r = sshkey_puts(key, buf)) != 0)
|
||||||
|
fatal("%s: couldn't put hostkey %d: %s",
|
||||||
|
__func__, i, ssh_err(r));
|
||||||
|
nkeys++;
|
||||||
|
}
|
||||||
|
if (nkeys == 0)
|
||||||
|
fatal("%s: no hostkeys", __func__);
|
||||||
|
debug3("%s: send %d hostkeys", __func__, nkeys);
|
||||||
|
packet_start(SSH2_MSG_GLOBAL_REQUEST);
|
||||||
|
packet_put_cstring("hostkeys@openssh.com");
|
||||||
|
packet_put_char(0); /* want-reply */
|
||||||
|
packet_put_string(sshbuf_ptr(buf), sshbuf_len(buf));
|
||||||
|
packet_send();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* returns 1 if connection should be dropped, 0 otherwise.
|
* returns 1 if connection should be dropped, 0 otherwise.
|
||||||
* dropping starts at connection #max_startups_begin with a probability
|
* dropping starts at connection #max_startups_begin with a probability
|
||||||
|
@ -1722,6 +1758,8 @@ main(int ac, char **av)
|
||||||
continue;
|
continue;
|
||||||
key = key_load_private(options.host_key_files[i], "", NULL);
|
key = key_load_private(options.host_key_files[i], "", NULL);
|
||||||
pubkey = key_load_public(options.host_key_files[i], NULL);
|
pubkey = key_load_public(options.host_key_files[i], NULL);
|
||||||
|
if (pubkey == NULL && key != NULL)
|
||||||
|
pubkey = key_demote(key);
|
||||||
sensitive_data.host_keys[i] = key;
|
sensitive_data.host_keys[i] = key;
|
||||||
sensitive_data.host_pubkeys[i] = pubkey;
|
sensitive_data.host_pubkeys[i] = pubkey;
|
||||||
|
|
||||||
|
@ -2185,6 +2223,10 @@ main(int ac, char **av)
|
||||||
packet_set_timeout(options.client_alive_interval,
|
packet_set_timeout(options.client_alive_interval,
|
||||||
options.client_alive_count_max);
|
options.client_alive_count_max);
|
||||||
|
|
||||||
|
/* Try to send all our hostkeys to the client */
|
||||||
|
if (compat20)
|
||||||
|
notify_hostkeys(active_state);
|
||||||
|
|
||||||
/* Start session. */
|
/* Start session. */
|
||||||
do_authenticated(authctxt);
|
do_authenticated(authctxt);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue