upstream commit

Revise hostkeys@openssh.com hostkey learning extension.

The client will not ask the server to prove ownership of the private
halves of any hitherto-unseen hostkeys it offers to the client.

Allow UpdateHostKeys option to take an 'ask' argument to let the
user manually review keys offered.

ok markus@
This commit is contained in:
djm@openbsd.org 2015-02-16 22:13:32 +00:00 committed by Damien Miller
parent 6c5c949782
commit 523463a3a2
14 changed files with 537 additions and 101 deletions

View File

@ -40,8 +40,8 @@ http://www.openssh.com/txt/draft-miller-secsh-compression-delayed-00.txt
"ecdsa-sha2-nistp521-cert-v01@openssh.com"
OpenSSH introduces new public key algorithms to support certificate
authentication for users and hostkeys. These methods are documented in
the file PROTOCOL.certkeys
authentication for users and host keys. These methods are documented
in the file PROTOCOL.certkeys
1.4. transport: Elliptic Curve cryptography
@ -283,26 +283,51 @@ by the client cancel the forwarding of a Unix domain socket.
string socket path
2.5. connection: hostkey update and rotation "hostkeys@openssh.com"
and "hostkeys-prove@openssh.com"
OpenSSH supports a protocol extension allowing a server to inform
a client of all its protocol v.2 hostkeys after user-authentication
a client of all its protocol v.2 host keys 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.
Upon receiving this message, a client should check which of the
supplied host keys are present in known_hosts. For keys that are
not present, it should send a "hostkeys-prove@openssh.com" message
to request the server prove ownership of the private half of the
key.
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.
byte SSH_MSG_GLOBAL_REQUEST
string "hostkeys-prove@openssh.com"
char 1 /* want-reply */
string[] hostkeys
When a server receives this message, it should generate a signature
using each requested key over the following:
string session identifier
string "hostkeys-prove@openssh.com"
string hostkey
These signatures should be included in the reply, in the order matching
the hostkeys in the request:
byte SSH_MSG_REQUEST_SUCCESS
string[] signatures
When the client receives this reply (and not a failure), it should
validate the signatures and 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.
These extensions let a client 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
@ -428,4 +453,4 @@ respond with a SSH_FXP_STATUS message.
This extension is advertised in the SSH_FXP_VERSION hello with version
"1".
$OpenBSD: PROTOCOL,v 1.25 2015/01/26 03:04:45 djm Exp $
$OpenBSD: PROTOCOL,v 1.26 2015/02/16 22:13:32 djm Exp $

7
auth.h
View File

@ -1,4 +1,4 @@
/* $OpenBSD: auth.h,v 1.81 2015/01/26 06:10:03 djm Exp $ */
/* $OpenBSD: auth.h,v 1.82 2015/02/16 22:13:32 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
@ -206,9 +206,10 @@ Key *get_hostkey_by_index(int);
Key *get_hostkey_public_by_index(int, struct ssh *);
Key *get_hostkey_public_by_type(int, int, struct ssh *);
Key *get_hostkey_private_by_type(int, int, struct ssh *);
int get_hostkey_index(Key *, struct ssh *);
int get_hostkey_index(Key *, int, struct ssh *);
int ssh1_session_key(BIGNUM *);
int sshd_hostkey_sign(Key *, Key *, u_char **, size_t *, u_char *, size_t, u_int);
int sshd_hostkey_sign(Key *, Key *, u_char **, size_t *,
const u_char *, size_t, u_int);
/* debug messages during authentication */
void auth_debug_add(const char *fmt,...) __attribute__((format(printf, 1, 2)));

View File

@ -1,4 +1,4 @@
/* $OpenBSD: clientloop.c,v 1.268 2015/02/16 22:08:57 djm Exp $ */
/* $OpenBSD: clientloop.c,v 1.269 2015/02/16 22:13:32 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -2089,6 +2089,216 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt)
return 0;
}
struct hostkeys_update_ctx {
/* The hostname and (optionally) IP address string for the server */
char *host_str, *ip_str;
/*
* Keys received from the server and a flag for each indicating
* whether they already exist in known_hosts.
* keys_seen is filled in by hostkeys_find() and later (for new
* keys) by client_global_hostkeys_private_confirm().
*/
struct sshkey **keys;
int *keys_seen;
size_t nkeys;
size_t nnew;
/*
* Keys that are in known_hosts, but were not present in the update
* from the server (i.e. scheduled to be deleted).
* Filled in by hostkeys_find().
*/
struct sshkey **old_keys;
size_t nold;
};
static void
hostkeys_update_ctx_free(struct hostkeys_update_ctx *ctx)
{
size_t i;
if (ctx == NULL)
return;
for (i = 0; i < ctx->nkeys; i++)
sshkey_free(ctx->keys[i]);
free(ctx->keys);
free(ctx->keys_seen);
for (i = 0; i < ctx->nold; i++)
sshkey_free(ctx->old_keys[i]);
free(ctx->old_keys);
free(ctx->host_str);
free(ctx->ip_str);
free(ctx);
}
static int
hostkeys_find(struct hostkey_foreach_line *l, void *_ctx)
{
struct hostkeys_update_ctx *ctx = (struct hostkeys_update_ctx *)_ctx;
size_t i;
struct sshkey **tmp;
if (l->status != HKF_STATUS_MATCHED || l->key == NULL ||
l->key->type == KEY_RSA1)
return 0;
/* Mark off keys we've already seen for this host */
for (i = 0; i < ctx->nkeys; i++) {
if (sshkey_equal(l->key, ctx->keys[i])) {
debug3("%s: found %s key at %s:%ld", __func__,
sshkey_ssh_name(ctx->keys[i]), l->path, l->linenum);
ctx->keys_seen[i] = 1;
return 0;
}
}
/* This line contained a key that not offered by the server */
debug3("%s: deprecated %s key at %s:%ld", __func__,
sshkey_ssh_name(l->key), l->path, l->linenum);
if ((tmp = reallocarray(ctx->old_keys, ctx->nold + 1,
sizeof(*ctx->old_keys))) == NULL)
fatal("%s: reallocarray failed nold = %zu",
__func__, ctx->nold);
ctx->old_keys = tmp;
ctx->old_keys[ctx->nold++] = l->key;
l->key = NULL;
return 0;
}
static void
update_known_hosts(struct hostkeys_update_ctx *ctx)
{
int r, loglevel = options.update_hostkeys == SSH_UPDATE_HOSTKEYS_ASK ?
SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_VERBOSE;
char *fp, *response;
size_t i;
for (i = 0; i < ctx->nkeys; i++) {
if (ctx->keys_seen[i] != 2)
continue;
if ((fp = sshkey_fingerprint(ctx->keys[i],
options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
fatal("%s: sshkey_fingerprint failed", __func__);
do_log2(loglevel, "Learned new hostkey: %s %s",
sshkey_type(ctx->keys[i]), fp);
free(fp);
}
for (i = 0; i < ctx->nold; i++) {
if ((fp = sshkey_fingerprint(ctx->old_keys[i],
options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
fatal("%s: sshkey_fingerprint failed", __func__);
do_log2(loglevel, "Deprecating obsolete hostkey: %s %s",
sshkey_type(ctx->old_keys[i]), fp);
free(fp);
}
if (options.update_hostkeys == SSH_UPDATE_HOSTKEYS_ASK) {
leave_raw_mode(options.request_tty == REQUEST_TTY_FORCE);
response = NULL;
for (i = 0; !quit_pending && i < 3; i++) {
free(response);
response = read_passphrase("Accept updated hostkeys? "
"(yes/no): ", RP_ECHO);
if (strcasecmp(response, "yes") == 0)
break;
else if (quit_pending || response == NULL ||
strcasecmp(response, "no") == 0) {
options.update_hostkeys = 0;
break;
} else {
do_log2(loglevel, "Please enter "
"\"yes\" or \"no\"");
}
}
if (quit_pending || i >= 3 || response == NULL)
options.update_hostkeys = 0;
free(response);
enter_raw_mode(options.request_tty == REQUEST_TTY_FORCE);
}
/*
* Now that all the keys are verified, we can go ahead and replace
* them in known_hosts (assuming SSH_UPDATE_HOSTKEYS_ASK didn't
* cancel the operation).
*/
if (options.update_hostkeys != 0 &&
(r = hostfile_replace_entries(options.user_hostfiles[0],
ctx->host_str, ctx->ip_str, ctx->keys, ctx->nkeys,
options.hash_known_hosts, 0,
options.fingerprint_hash)) != 0)
error("%s: hostfile_replace_entries failed: %s",
__func__, ssh_err(r));
}
static void
client_global_hostkeys_private_confirm(int type, u_int32_t seq, void *_ctx)
{
struct ssh *ssh = active_state; /* XXX */
struct hostkeys_update_ctx *ctx = (struct hostkeys_update_ctx *)_ctx;
size_t i, ndone;
struct sshbuf *signdata;
int r;
const u_char *sig;
size_t siglen;
if (ctx->nnew == 0)
fatal("%s: ctx->nnew == 0", __func__); /* sanity */
if (type != SSH2_MSG_REQUEST_SUCCESS) {
error("Server failed to confirm ownership of "
"private host keys");
hostkeys_update_ctx_free(ctx);
return;
}
if ((signdata = sshbuf_new()) == NULL)
fatal("%s: sshbuf_new failed", __func__);
/* Don't want to accidentally accept an unbound signature */
if (ssh->kex->session_id_len == 0)
fatal("%s: ssh->kex->session_id_len == 0", __func__);
/*
* Expect a signature for each of the ctx->nnew private keys we
* haven't seen before. They will be in the same order as the
* ctx->keys where the corresponding ctx->keys_seen[i] == 0.
*/
for (ndone = i = 0; i < ctx->nkeys; i++) {
if (ctx->keys_seen[i])
continue;
/* Prepare data to be signed: session ID, unique string, key */
sshbuf_reset(signdata);
if ((r = sshbuf_put_string(signdata, ssh->kex->session_id,
ssh->kex->session_id_len)) != 0 ||
(r = sshbuf_put_cstring(signdata,
"hostkeys-prove@openssh.com")) != 0 ||
(r = sshkey_puts(ctx->keys[i], signdata)) != 0)
fatal("%s: failed to prepare signature: %s",
__func__, ssh_err(r));
/* Extract and verify signature */
if ((r = sshpkt_get_string_direct(ssh, &sig, &siglen)) != 0) {
error("%s: couldn't parse message: %s",
__func__, ssh_err(r));
goto out;
}
if ((r = sshkey_verify(ctx->keys[i], sig, siglen,
sshbuf_ptr(signdata), sshbuf_len(signdata), 0)) != 0) {
error("%s: server gave bad signature for %s key %zu",
__func__, sshkey_type(ctx->keys[i]), i);
goto out;
}
/* Key is good. Mark it as 'seen' */
ctx->keys_seen[i] = 2;
ndone++;
}
if (ndone != ctx->nnew)
fatal("%s: ndone != ctx->nnew (%zu / %zu)", __func__,
ndone, ctx->nnew); /* Shouldn't happen */
ssh_packet_check_eom(ssh);
/* Make the edits to known_hosts */
update_known_hosts(ctx);
out:
hostkeys_update_ctx_free(ctx);
}
/*
* Handle hostkeys@openssh.com global request to inform the client of all
* the server's hostkeys. The keys are checked against the user's
@ -2097,34 +2307,35 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt)
static int
client_input_hostkeys(void)
{
struct ssh *ssh = active_state; /* XXX */
const u_char *blob = NULL;
u_int i, len = 0, nkeys = 0;
size_t i, len = 0;
struct sshbuf *buf = NULL;
struct sshkey *key = NULL, **tmp, **keys = NULL;
int r, success = 1;
char *fp, *host_str = NULL, *ip_str = NULL;
struct sshkey *key = NULL, **tmp;
int r;
char *fp;
static int hostkeys_seen = 0; /* XXX use struct ssh */
extern struct sockaddr_storage hostaddr; /* XXX from ssh.c */
struct hostkeys_update_ctx *ctx;
/*
* 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();
ctx = xcalloc(1, sizeof(*ctx));
if (hostkeys_seen)
fatal("%s: server already sent hostkeys", __func__);
if (options.update_hostkeys == SSH_UPDATE_HOSTKEYS_ASK &&
options.batch_mode)
return 1; /* won't ask in batchmode, so don't even try */
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) {
while (ssh_packet_remaining(ssh) > 0) {
sshkey_free(key);
key = NULL;
if ((r = sshkey_froms(buf, &key)) != 0)
if ((r = sshpkt_get_string_direct(ssh, &blob, &len)) != 0) {
error("%s: couldn't parse message: %s",
__func__, ssh_err(r));
goto out;
}
if ((r = sshkey_from_blob(blob, len, &key)) != 0)
fatal("%s: parse key: %s", __func__, ssh_err(r));
fp = sshkey_fingerprint(key, options.fingerprint_hash,
SSH_FP_DEFAULT);
@ -2140,47 +2351,107 @@ client_input_hostkeys(void)
__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;
/* Skip certs */
if (sshkey_is_cert(key)) {
debug3("%s: %s key is a certificate; skipping",
__func__, sshkey_ssh_name(key));
continue;
}
/* Ensure keys are unique */
for (i = 0; i < ctx->nkeys; i++) {
if (sshkey_equal(key, ctx->keys[i])) {
error("%s: received duplicated %s host key",
__func__, sshkey_ssh_name(key));
goto out;
}
}
/* Key is good, record it */
if ((tmp = reallocarray(ctx->keys, ctx->nkeys + 1,
sizeof(*ctx->keys))) == NULL)
fatal("%s: reallocarray failed nkeys = %zu",
__func__, ctx->nkeys);
ctx->keys = tmp;
ctx->keys[ctx->nkeys++] = key;
key = NULL;
}
if (nkeys == 0) {
if (ctx->nkeys == 0) {
error("%s: server sent no hostkeys", __func__);
goto out;
}
if ((ctx->keys_seen = calloc(ctx->nkeys,
sizeof(*ctx->keys_seen))) == NULL)
fatal("%s: calloc failed", __func__);
get_hostfile_hostname_ipaddr(host,
options.check_host_ip ? (struct sockaddr *)&hostaddr : NULL,
options.port, &host_str, options.check_host_ip ? &ip_str : NULL);
options.port, &ctx->host_str,
options.check_host_ip ? &ctx->ip_str : NULL);
debug3("%s: update known hosts for %s%s%s with %u keys from server",
__func__, host_str,
options.check_host_ip ? " " : "",
options.check_host_ip ? ip_str : "", nkeys);
if ((r = hostfile_replace_entries(options.user_hostfiles[0],
host_str, options.check_host_ip ? ip_str : NULL,
keys, nkeys, options.hash_known_hosts, 0,
options.fingerprint_hash)) != 0) {
error("%s: hostfile_replace_entries failed: %s",
__func__, ssh_err(r));
/* Find which keys we already know about. */
if ((r = hostkeys_foreach(options.user_hostfiles[0], hostkeys_find,
ctx, ctx->host_str, ctx->ip_str,
HKF_WANT_PARSE_KEY|HKF_WANT_MATCH)) != 0) {
error("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r));
goto out;
}
/* Figure out if we have any new keys to add */
ctx->nnew = 0;
for (i = 0; i < ctx->nkeys; i++) {
if (!ctx->keys_seen[i])
ctx->nnew++;
}
debug3("%s: %zu keys from server: %zu new, %zu retained. %zu to remove",
__func__, ctx->nkeys, ctx->nnew, ctx->nkeys - ctx->nnew, ctx->nold);
if (ctx->nnew == 0 && ctx->nold != 0) {
/* We have some keys to remove. Just do it. */
update_known_hosts(ctx);
} else if (ctx->nnew != 0) {
/*
* We have received hitherto-unseen keys from the server.
* Ask the server to confirm ownership of the private halves.
*/
debug3("%s: asking server to prove ownership for %zu keys",
__func__, ctx->nnew);
if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 ||
(r = sshpkt_put_cstring(ssh,
"hostkeys-prove@openssh.com")) != 0 ||
(r = sshpkt_put_u8(ssh, 1)) != 0) /* bool: want reply */
fatal("%s: cannot prepare packet: %s",
__func__, ssh_err(r));
if ((buf = sshbuf_new()) == NULL)
fatal("%s: sshbuf_new", __func__);
for (i = 0; i < ctx->nkeys; i++) {
if (ctx->keys_seen[i])
continue;
sshbuf_reset(buf);
if ((r = sshkey_putb(ctx->keys[i], buf)) != 0)
fatal("%s: sshkey_putb: %s",
__func__, ssh_err(r));
if ((r = sshpkt_put_stringb(ssh, buf)) != 0)
fatal("%s: sshpkt_put_string: %s",
__func__, ssh_err(r));
}
if ((r = sshpkt_send(ssh)) != 0)
fatal("%s: sshpkt_send: %s", __func__, ssh_err(r));
client_register_global_confirm(
client_global_hostkeys_private_confirm, ctx);
ctx = NULL; /* will be freed in callback */
}
/* Success */
out:
free(host_str);
free(ip_str);
hostkeys_update_ctx_free(ctx);
sshkey_free(key);
for (i = 0; i < nkeys; i++)
sshkey_free(keys[i]);
sshbuf_free(buf);
return success;
/*
* NB. Return success for all cases. The server doesn't need to know
* what the client does with its hosts file.
*/
return 1;
}
static int

6
kex.h
View File

@ -1,4 +1,4 @@
/* $OpenBSD: kex.h,v 1.70 2015/01/26 06:10:03 djm Exp $ */
/* $OpenBSD: kex.h,v 1.71 2015/02/16 22:13:32 djm Exp $ */
/*
* Copyright (c) 2000, 2001 Markus Friedl. All rights reserved.
@ -130,9 +130,9 @@ struct kex {
int (*verify_host_key)(struct sshkey *, struct ssh *);
struct sshkey *(*load_host_public_key)(int, int, struct ssh *);
struct sshkey *(*load_host_private_key)(int, int, struct ssh *);
int (*host_key_index)(struct sshkey *, struct ssh *);
int (*host_key_index)(struct sshkey *, int, struct ssh *);
int (*sign)(struct sshkey *, struct sshkey *,
u_char **, size_t *, u_char *, size_t, u_int);
u_char **, size_t *, const u_char *, size_t, u_int);
int (*kex[KEX_MAX])(struct ssh *);
/* kex specific state */
DH *dh; /* DH */

View File

@ -1,4 +1,4 @@
/* $OpenBSD: monitor.c,v 1.143 2015/02/13 18:57:00 markus Exp $ */
/* $OpenBSD: monitor.c,v 1.144 2015/02/16 22:13:32 djm Exp $ */
/*
* Copyright 2002 Niels Provos <provos@citi.umich.edu>
* Copyright 2002 Markus Friedl <markus@openbsd.org>
@ -685,12 +685,15 @@ mm_answer_moduli(int sock, Buffer *m)
int
mm_answer_sign(int sock, Buffer *m)
{
struct ssh *ssh = active_state; /* XXX */
extern int auth_sock; /* XXX move to state struct? */
struct sshkey *key;
struct sshbuf *sigbuf;
u_char *p;
u_char *signature;
size_t datlen, siglen;
int r, keyid;
int r, keyid, is_proof = 0;
const char proof_req[] = "hostkeys-prove@openssh.com";
debug3("%s", __func__);
@ -701,9 +704,38 @@ mm_answer_sign(int sock, Buffer *m)
/*
* Supported KEX types use SHA1 (20 bytes), SHA256 (32 bytes),
* SHA384 (48 bytes) and SHA512 (64 bytes).
*
* Otherwise, verify the signature request is for a hostkey
* proof.
*
* XXX perform similar check for KEX signature requests too?
* it's not trivial, since what is signed is the hash, rather
* than the full kex structure...
*/
if (datlen != 20 && datlen != 32 && datlen != 48 && datlen != 64)
fatal("%s: data length incorrect: %zu", __func__, datlen);
if (datlen != 20 && datlen != 32 && datlen != 48 && datlen != 64) {
/*
* Construct expected hostkey proof and compare it to what
* the client sent us.
*/
if (session_id2_len == 0) /* hostkeys is never first */
fatal("%s: bad data length: %zu", __func__, datlen);
if ((key = get_hostkey_public_by_index(keyid, ssh)) == NULL)
fatal("%s: no hostkey for index %d", __func__, keyid);
if ((sigbuf = sshbuf_new()) == NULL)
fatal("%s: sshbuf_new", __func__);
if ((r = sshbuf_put_string(sigbuf, session_id2,
session_id2_len) != 0) ||
(r = sshbuf_put_cstring(sigbuf, proof_req)) != 0 ||
(r = sshkey_puts(key, sigbuf)) != 0)
fatal("%s: couldn't prepare private key "
"proof buffer: %s", __func__, ssh_err(r));
if (datlen != sshbuf_len(sigbuf) ||
memcmp(p, sshbuf_ptr(sigbuf), sshbuf_len(sigbuf)) != 0)
fatal("%s: bad data length: %zu, hostkey proof len %zu",
__func__, datlen, sshbuf_len(sigbuf));
sshbuf_free(sigbuf);
is_proof = 1;
}
/* save session id, it will be passed on the first call */
if (session_id2_len == 0) {
@ -717,7 +749,7 @@ mm_answer_sign(int sock, Buffer *m)
datafellows)) != 0)
fatal("%s: sshkey_sign failed: %s",
__func__, ssh_err(r));
} else if ((key = get_hostkey_public_by_index(keyid, active_state)) != NULL &&
} else if ((key = get_hostkey_public_by_index(keyid, ssh)) != NULL &&
auth_sock > 0) {
if ((r = ssh_agent_sign(auth_sock, key, &signature, &siglen,
p, datlen, datafellows)) != 0) {
@ -727,7 +759,8 @@ mm_answer_sign(int sock, Buffer *m)
} else
fatal("%s: no hostkey from index %d", __func__, keyid);
debug3("%s: signature %p(%zu)", __func__, signature, siglen);
debug3("%s: %s signature %p(%zu)", __func__,
is_proof ? "KEX" : "hostkey proof", signature, siglen);
sshbuf_reset(m);
if ((r = sshbuf_put_string(m, signature, siglen)) != 0)

View File

@ -1,4 +1,4 @@
/* $OpenBSD: monitor_wrap.c,v 1.83 2015/01/19 20:16:15 markus Exp $ */
/* $OpenBSD: monitor_wrap.c,v 1.84 2015/02/16 22:13:32 djm Exp $ */
/*
* Copyright 2002 Niels Provos <provos@citi.umich.edu>
* Copyright 2002 Markus Friedl <markus@openbsd.org>
@ -219,7 +219,8 @@ mm_choose_dh(int min, int nbits, int max)
#endif
int
mm_key_sign(Key *key, u_char **sigp, u_int *lenp, u_char *data, u_int datalen)
mm_key_sign(Key *key, u_char **sigp, u_int *lenp,
const u_char *data, u_int datalen)
{
struct kex *kex = *pmonitor->m_pkex;
Buffer m;
@ -227,7 +228,7 @@ mm_key_sign(Key *key, u_char **sigp, u_int *lenp, u_char *data, u_int datalen)
debug3("%s entering", __func__);
buffer_init(&m);
buffer_put_int(&m, kex->host_key_index(key, active_state));
buffer_put_int(&m, kex->host_key_index(key, 0, active_state));
buffer_put_string(&m, data, datalen);
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_SIGN, &m);

View File

@ -1,4 +1,4 @@
/* $OpenBSD: monitor_wrap.h,v 1.25 2015/01/19 19:52:16 markus Exp $ */
/* $OpenBSD: monitor_wrap.h,v 1.26 2015/02/16 22:13:32 djm Exp $ */
/*
* Copyright 2002 Niels Provos <provos@citi.umich.edu>
@ -40,7 +40,7 @@ struct Authctxt;
void mm_log_handler(LogLevel, const char *, void *);
int mm_is_monitor(void);
DH *mm_choose_dh(int, int, int);
int mm_key_sign(Key *, u_char **, u_int *, u_char *, u_int);
int mm_key_sign(Key *, u_char **, u_int *, const u_char *, u_int);
void mm_inform_authserv(char *, char *);
struct passwd *mm_getpwnamallow(const char *);
char *mm_auth2_read_banner(void);

View File

@ -1,4 +1,4 @@
/* $OpenBSD: readconf.c,v 1.231 2015/02/02 07:41:40 djm Exp $ */
/* $OpenBSD: readconf.c,v 1.232 2015/02/16 22:13:32 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -1480,7 +1480,8 @@ parse_int:
case oUpdateHostkeys:
intptr = &options->update_hostkeys;
goto parse_flag;
multistate_ptr = multistate_yesnoask;
goto parse_multistate;
case oHostbasedKeyTypes:
charptr = &options->hostbased_key_types;
@ -2107,6 +2108,7 @@ fmt_intarg(OpCodes code, int val)
return fmt_multistate_int(val, multistate_addressfamily);
case oVerifyHostKeyDNS:
case oStrictHostKeyChecking:
case oUpdateHostkeys:
return fmt_multistate_int(val, multistate_yesnoask);
case oControlMaster:
return fmt_multistate_int(val, multistate_controlmaster);

View File

@ -1,4 +1,4 @@
/* $OpenBSD: readconf.h,v 1.108 2015/01/30 11:43:14 djm Exp $ */
/* $OpenBSD: readconf.h,v 1.109 2015/02/16 22:13:32 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@ -148,7 +148,7 @@ typedef struct {
int fingerprint_hash;
int update_hostkeys;
int update_hostkeys; /* one of SSH_UPDATE_HOSTKEYS_* */
char *hostbased_key_types;
@ -174,6 +174,10 @@ typedef struct {
#define SSHCONF_USERCONF 2 /* user provided config file not system */
#define SSHCONF_POSTCANON 4 /* After hostname canonicalisation */
#define SSH_UPDATE_HOSTKEYS_NO 0
#define SSH_UPDATE_HOSTKEYS_YES 1
#define SSH_UPDATE_HOSTKEYS_ASK 2
void initialize_options(Options *);
void fill_default_options(Options *);
void fill_default_options_for_canonicalization(Options *);

View File

@ -1,4 +1,4 @@
/* $OpenBSD: serverloop.c,v 1.176 2015/01/20 23:14:00 deraadt Exp $ */
/* $OpenBSD: serverloop.c,v 1.177 2015/02/16 22:13:32 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -79,6 +79,7 @@
#include "auth-options.h"
#include "serverloop.h"
#include "roaming.h"
#include "ssherr.h"
extern ServerOptions options;
@ -1149,12 +1150,83 @@ server_input_channel_open(int type, u_int32_t seq, void *ctxt)
return 0;
}
static int
server_input_hostkeys_prove(struct sshbuf **respp)
{
struct ssh *ssh = active_state; /* XXX */
struct sshbuf *resp = NULL;
struct sshbuf *sigbuf = NULL;
struct sshkey *key = NULL, *key_pub = NULL, *key_prv = NULL;
int r, ndx, success = 0;
const u_char *blob;
u_char *sig = 0;
size_t blen, slen;
if ((resp = sshbuf_new()) == NULL || (sigbuf = sshbuf_new()) == NULL)
fatal("%s: sshbuf_new", __func__);
while (ssh_packet_remaining(ssh) > 0) {
sshkey_free(key);
key = NULL;
if ((r = sshpkt_get_string_direct(ssh, &blob, &blen)) != 0 ||
(r = sshkey_from_blob(blob, blen, &key)) != 0) {
error("%s: couldn't parse key: %s",
__func__, ssh_err(r));
goto out;
}
/*
* Better check that this is actually one of our hostkeys
* before attempting to sign anything with it.
*/
if ((ndx = ssh->kex->host_key_index(key, 1, ssh)) == -1) {
error("%s: unknown host %s key",
__func__, sshkey_type(key));
goto out;
}
/*
* XXX refactor: make kex->sign just use an index rather
* than passing in public and private keys
*/
if ((key_prv = get_hostkey_by_index(ndx)) == NULL &&
(key_pub = get_hostkey_public_by_index(ndx, ssh)) == NULL) {
error("%s: can't retrieve hostkey %d", __func__, ndx);
goto out;
}
sshbuf_reset(sigbuf);
free(sig);
sig = NULL;
if ((r = sshbuf_put_string(sigbuf,
ssh->kex->session_id, ssh->kex->session_id_len)) != 0 ||
(r = sshbuf_put_cstring(sigbuf,
"hostkeys-prove@openssh.com")) != 0 ||
(r = sshkey_puts(key, sigbuf)) != 0 ||
(r = ssh->kex->sign(key_prv, key_pub, &sig, &slen,
sshbuf_ptr(sigbuf), sshbuf_len(sigbuf), 0)) != 0 ||
(r = sshbuf_put_string(resp, sig, slen)) != 0) {
error("%s: couldn't prepare signature: %s",
__func__, ssh_err(r));
goto out;
}
}
/* Success */
*respp = resp;
resp = NULL; /* don't free it */
success = 1;
out:
free(sig);
sshbuf_free(resp);
sshbuf_free(sigbuf);
sshkey_free(key);
return success;
}
static int
server_input_global_request(int type, u_int32_t seq, void *ctxt)
{
char *rtype;
int want_reply;
int success = 0, allocated_listen_port = 0;
int r, success = 0, allocated_listen_port = 0;
struct sshbuf *resp = NULL;
rtype = packet_get_string(NULL);
want_reply = packet_get_char();
@ -1191,6 +1263,10 @@ server_input_global_request(int type, u_int32_t seq, void *ctxt)
&allocated_listen_port, &options.fwd_opts);
}
free(fwd.listen_host);
if ((resp = sshbuf_new()) == NULL)
fatal("%s: sshbuf_new", __func__);
if ((r = sshbuf_put_u32(resp, allocated_listen_port)) != 0)
fatal("%s: sshbuf_put_u32: %s", __func__, ssh_err(r));
} else if (strcmp(rtype, "cancel-tcpip-forward") == 0) {
struct Forward fwd;
@ -1234,16 +1310,20 @@ server_input_global_request(int type, u_int32_t seq, void *ctxt)
} else if (strcmp(rtype, "no-more-sessions@openssh.com") == 0) {
no_more_sessions = 1;
success = 1;
} else if (strcmp(rtype, "hostkeys-prove@openssh.com") == 0) {
success = server_input_hostkeys_prove(&resp);
}
if (want_reply) {
packet_start(success ?
SSH2_MSG_REQUEST_SUCCESS : SSH2_MSG_REQUEST_FAILURE);
if (success && allocated_listen_port > 0)
packet_put_int(allocated_listen_port);
if (success && resp != NULL)
ssh_packet_put_raw(active_state, sshbuf_ptr(resp),
sshbuf_len(resp));
packet_send();
packet_write_wait();
}
free(rtype);
sshbuf_free(resp);
return 0;
}

View File

@ -1,4 +1,4 @@
/* $OpenBSD: ssh_api.c,v 1.3 2015/01/30 01:13:33 djm Exp $ */
/* $OpenBSD: ssh_api.c,v 1.4 2015/02/16 22:13:32 djm Exp $ */
/*
* Copyright (c) 2012 Markus Friedl. All rights reserved.
*
@ -41,7 +41,7 @@ int _ssh_verify_host_key(struct sshkey *, struct ssh *);
struct sshkey *_ssh_host_public_key(int, int, struct ssh *);
struct sshkey *_ssh_host_private_key(int, int, struct ssh *);
int _ssh_host_key_sign(struct sshkey *, struct sshkey *, u_char **,
size_t *, u_char *, size_t, u_int);
size_t *, const u_char *, size_t, u_int);
/*
* stubs for the server side implementation of kex.
@ -524,7 +524,8 @@ _ssh_order_hostkeyalgs(struct ssh *ssh)
int
_ssh_host_key_sign(struct sshkey *privkey, struct sshkey *pubkey,
u_char **signature, size_t *slen, u_char *data, size_t dlen, u_int compat)
u_char **signature, size_t *slen,
const u_char *data, size_t dlen, u_int compat)
{
return sshkey_sign(privkey, signature, slen, data, dlen, compat);
}

View File

@ -33,8 +33,8 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
.\" $OpenBSD: ssh_config.5,v 1.203 2015/02/02 07:41:40 djm Exp $
.Dd $Mdocdate: February 2 2015 $
.\" $OpenBSD: ssh_config.5,v 1.204 2015/02/16 22:13:32 djm Exp $
.Dd $Mdocdate: February 16 2015 $
.Dt SSH_CONFIG 5
.Os
.Sh NAME
@ -1510,15 +1510,20 @@ 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
or
.Dq yes ,
.Dq no
(the default).
(the default) or
.Dq ask .
Enabling this option allows learning alternate hostkeys for a server
and supports graceful key rotation by allowing a server to send replacement
public 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.
If
.Cm UpdateHostKeys
is set to
.Dq ask ,
then the user is asked to confirm the modifications to the known_hosts file.
.Pp
Presently, only
.Xr sshd 8

35
sshd.c
View File

@ -1,4 +1,4 @@
/* $OpenBSD: sshd.c,v 1.441 2015/01/31 20:30:05 djm Exp $ */
/* $OpenBSD: sshd.c,v 1.442 2015/02/16 22:13:32 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -894,18 +894,25 @@ get_hostkey_public_by_index(int ind, struct ssh *ssh)
}
int
get_hostkey_index(Key *key, struct ssh *ssh)
get_hostkey_index(Key *key, int compare, struct ssh *ssh)
{
int i;
for (i = 0; i < options.num_host_key_files; i++) {
if (key_is_cert(key)) {
if (key == sensitive_data.host_certificates[i])
if (key == sensitive_data.host_certificates[i] ||
(compare && sensitive_data.host_certificates[i] &&
sshkey_equal(key,
sensitive_data.host_certificates[i])))
return (i);
} else {
if (key == sensitive_data.host_keys[i])
if (key == sensitive_data.host_keys[i] ||
(compare && sensitive_data.host_keys[i] &&
sshkey_equal(key, sensitive_data.host_keys[i])))
return (i);
if (key == sensitive_data.host_pubkeys[i])
if (key == sensitive_data.host_pubkeys[i] ||
(compare && sensitive_data.host_pubkeys[i] &&
sshkey_equal(key, sensitive_data.host_pubkeys[i])))
return (i);
}
}
@ -933,19 +940,23 @@ notify_hostkeys(struct ssh *ssh)
debug3("%s: key %d: %s %s", __func__, i,
sshkey_ssh_name(key), fp);
free(fp);
if ((r = sshkey_puts(key, buf)) != 0)
if (nkeys == 0) {
packet_start(SSH2_MSG_GLOBAL_REQUEST);
packet_put_cstring("hostkeys@openssh.com");
packet_put_char(0); /* want-reply */
}
sshbuf_reset(buf);
if ((r = sshkey_putb(key, buf)) != 0)
fatal("%s: couldn't put hostkey %d: %s",
__func__, i, ssh_err(r));
packet_put_string(sshbuf_ptr(buf), sshbuf_len(buf));
nkeys++;
}
debug3("%s: sent %d hostkeys", __func__, 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();
sshbuf_free(buf);
}
/*
@ -2484,7 +2495,7 @@ do_ssh1_kex(void)
int
sshd_hostkey_sign(Key *privkey, Key *pubkey, u_char **signature, size_t *slen,
u_char *data, size_t dlen, u_int flag)
const u_char *data, size_t dlen, u_int flag)
{
int r;
u_int xxx_slen, xxx_dlen = dlen;

View File

@ -1,4 +1,4 @@
/* $OpenBSD: ssherr.c,v 1.3 2015/01/30 01:13:33 djm Exp $ */
/* $OpenBSD: ssherr.c,v 1.4 2015/02/16 22:13:32 djm Exp $ */
/*
* Copyright (c) 2011 Damien Miller
*
@ -121,6 +121,8 @@ ssh_err(int n)
return "agent not present";
case SSH_ERR_AGENT_NO_IDENTITIES:
return "agent contains no identities";
case SSH_ERR_BUFFER_READ_ONLY:
return "internal error: buffer is read-only";
case SSH_ERR_KRL_BAD_MAGIC:
return "KRL file has invalid magic number";
case SSH_ERR_KEY_REVOKED: