mirror of
git://anongit.mindrot.org/openssh.git
synced 2024-12-05 03:46:48 +08:00
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:
parent
6c5c949782
commit
523463a3a2
53
PROTOCOL
53
PROTOCOL
@ -40,8 +40,8 @@ http://www.openssh.com/txt/draft-miller-secsh-compression-delayed-00.txt
|
|||||||
"ecdsa-sha2-nistp521-cert-v01@openssh.com"
|
"ecdsa-sha2-nistp521-cert-v01@openssh.com"
|
||||||
|
|
||||||
OpenSSH introduces new public key algorithms to support certificate
|
OpenSSH introduces new public key algorithms to support certificate
|
||||||
authentication for users and hostkeys. These methods are documented in
|
authentication for users and host keys. These methods are documented
|
||||||
the file PROTOCOL.certkeys
|
in the file PROTOCOL.certkeys
|
||||||
|
|
||||||
1.4. transport: Elliptic Curve cryptography
|
1.4. transport: Elliptic Curve cryptography
|
||||||
|
|
||||||
@ -283,26 +283,51 @@ by the client cancel the forwarding of a Unix domain socket.
|
|||||||
string socket path
|
string socket path
|
||||||
|
|
||||||
2.5. connection: hostkey update and rotation "hostkeys@openssh.com"
|
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
|
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.
|
has completed.
|
||||||
|
|
||||||
byte SSH_MSG_GLOBAL_REQUEST
|
byte SSH_MSG_GLOBAL_REQUEST
|
||||||
string "hostkeys@openssh.com"
|
string "hostkeys@openssh.com"
|
||||||
string[] hostkeys
|
string[] hostkeys
|
||||||
|
|
||||||
Upon receiving this message, a client may update its known_hosts
|
Upon receiving this message, a client should check which of the
|
||||||
file, adding keys that it has not seen before and deleting keys
|
supplied host keys are present in known_hosts. For keys that are
|
||||||
for the server host that are no longer offered.
|
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
|
byte SSH_MSG_GLOBAL_REQUEST
|
||||||
not previously encountered, thereby allowing it to potentially
|
string "hostkeys-prove@openssh.com"
|
||||||
upgrade from weaker key algorithms to better ones. It also
|
char 1 /* want-reply */
|
||||||
supports graceful key rotation: a server may offer multiple keys
|
string[] hostkeys
|
||||||
of the same type for a period (to give clients an opportunity to
|
|
||||||
learn them using this extension) before removing the deprecated
|
When a server receives this message, it should generate a signature
|
||||||
key from those offered.
|
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
|
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
|
This extension is advertised in the SSH_FXP_VERSION hello with version
|
||||||
"1".
|
"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
7
auth.h
@ -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.
|
* 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_index(int, struct ssh *);
|
||||||
Key *get_hostkey_public_by_type(int, int, struct ssh *);
|
Key *get_hostkey_public_by_type(int, int, struct ssh *);
|
||||||
Key *get_hostkey_private_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 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 */
|
/* debug messages during authentication */
|
||||||
void auth_debug_add(const char *fmt,...) __attribute__((format(printf, 1, 2)));
|
void auth_debug_add(const char *fmt,...) __attribute__((format(printf, 1, 2)));
|
||||||
|
353
clientloop.c
353
clientloop.c
@ -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>
|
* 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
|
||||||
@ -2089,6 +2089,216 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt)
|
|||||||
return 0;
|
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
|
* Handle hostkeys@openssh.com global request to inform the client of all
|
||||||
* the server's hostkeys. The keys are checked against the user's
|
* 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
|
static int
|
||||||
client_input_hostkeys(void)
|
client_input_hostkeys(void)
|
||||||
{
|
{
|
||||||
|
struct ssh *ssh = active_state; /* XXX */
|
||||||
const u_char *blob = NULL;
|
const u_char *blob = NULL;
|
||||||
u_int i, len = 0, nkeys = 0;
|
size_t i, len = 0;
|
||||||
struct sshbuf *buf = NULL;
|
struct sshbuf *buf = NULL;
|
||||||
struct sshkey *key = NULL, **tmp, **keys = NULL;
|
struct sshkey *key = NULL, **tmp;
|
||||||
int r, success = 1;
|
int r;
|
||||||
char *fp, *host_str = NULL, *ip_str = NULL;
|
char *fp;
|
||||||
static int hostkeys_seen = 0; /* XXX use struct ssh */
|
static int hostkeys_seen = 0; /* XXX use struct ssh */
|
||||||
extern struct sockaddr_storage hostaddr; /* XXX from ssh.c */
|
extern struct sockaddr_storage hostaddr; /* XXX from ssh.c */
|
||||||
|
struct hostkeys_update_ctx *ctx;
|
||||||
|
|
||||||
/*
|
ctx = xcalloc(1, sizeof(*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();
|
|
||||||
|
|
||||||
if (hostkeys_seen)
|
if (hostkeys_seen)
|
||||||
fatal("%s: server already sent hostkeys", __func__);
|
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)
|
if (!options.update_hostkeys || options.num_user_hostfiles <= 0)
|
||||||
return 1;
|
return 1;
|
||||||
if ((buf = sshbuf_from(blob, len)) == NULL)
|
while (ssh_packet_remaining(ssh) > 0) {
|
||||||
fatal("%s: sshbuf_from failed", __func__);
|
|
||||||
while (sshbuf_len(buf) > 0) {
|
|
||||||
sshkey_free(key);
|
sshkey_free(key);
|
||||||
key = NULL;
|
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));
|
fatal("%s: parse key: %s", __func__, ssh_err(r));
|
||||||
fp = sshkey_fingerprint(key, options.fingerprint_hash,
|
fp = sshkey_fingerprint(key, options.fingerprint_hash,
|
||||||
SSH_FP_DEFAULT);
|
SSH_FP_DEFAULT);
|
||||||
@ -2140,47 +2351,107 @@ client_input_hostkeys(void)
|
|||||||
__func__, sshkey_ssh_name(key));
|
__func__, sshkey_ssh_name(key));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ((tmp = reallocarray(keys, nkeys + 1,
|
/* Skip certs */
|
||||||
sizeof(*keys))) == NULL)
|
if (sshkey_is_cert(key)) {
|
||||||
fatal("%s: reallocarray failed nkeys = %u",
|
debug3("%s: %s key is a certificate; skipping",
|
||||||
__func__, nkeys);
|
__func__, sshkey_ssh_name(key));
|
||||||
keys = tmp;
|
continue;
|
||||||
keys[nkeys++] = key;
|
}
|
||||||
|
/* 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;
|
key = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nkeys == 0) {
|
if (ctx->nkeys == 0) {
|
||||||
error("%s: server sent no hostkeys", __func__);
|
error("%s: server sent no hostkeys", __func__);
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
if ((ctx->keys_seen = calloc(ctx->nkeys,
|
||||||
|
sizeof(*ctx->keys_seen))) == NULL)
|
||||||
|
fatal("%s: calloc failed", __func__);
|
||||||
|
|
||||||
get_hostfile_hostname_ipaddr(host,
|
get_hostfile_hostname_ipaddr(host,
|
||||||
options.check_host_ip ? (struct sockaddr *)&hostaddr : NULL,
|
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",
|
/* Find which keys we already know about. */
|
||||||
__func__, host_str,
|
if ((r = hostkeys_foreach(options.user_hostfiles[0], hostkeys_find,
|
||||||
options.check_host_ip ? " " : "",
|
ctx, ctx->host_str, ctx->ip_str,
|
||||||
options.check_host_ip ? ip_str : "", nkeys);
|
HKF_WANT_PARSE_KEY|HKF_WANT_MATCH)) != 0) {
|
||||||
|
error("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r));
|
||||||
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));
|
|
||||||
goto out;
|
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 */
|
/* Success */
|
||||||
out:
|
out:
|
||||||
free(host_str);
|
hostkeys_update_ctx_free(ctx);
|
||||||
free(ip_str);
|
|
||||||
sshkey_free(key);
|
sshkey_free(key);
|
||||||
for (i = 0; i < nkeys; i++)
|
|
||||||
sshkey_free(keys[i]);
|
|
||||||
sshbuf_free(buf);
|
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
|
static int
|
||||||
|
6
kex.h
6
kex.h
@ -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.
|
* Copyright (c) 2000, 2001 Markus Friedl. All rights reserved.
|
||||||
@ -130,9 +130,9 @@ struct kex {
|
|||||||
int (*verify_host_key)(struct sshkey *, struct ssh *);
|
int (*verify_host_key)(struct sshkey *, struct ssh *);
|
||||||
struct sshkey *(*load_host_public_key)(int, int, struct ssh *);
|
struct sshkey *(*load_host_public_key)(int, int, struct ssh *);
|
||||||
struct sshkey *(*load_host_private_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 *,
|
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 *);
|
int (*kex[KEX_MAX])(struct ssh *);
|
||||||
/* kex specific state */
|
/* kex specific state */
|
||||||
DH *dh; /* DH */
|
DH *dh; /* DH */
|
||||||
|
45
monitor.c
45
monitor.c
@ -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 Niels Provos <provos@citi.umich.edu>
|
||||||
* Copyright 2002 Markus Friedl <markus@openbsd.org>
|
* Copyright 2002 Markus Friedl <markus@openbsd.org>
|
||||||
@ -685,12 +685,15 @@ mm_answer_moduli(int sock, Buffer *m)
|
|||||||
int
|
int
|
||||||
mm_answer_sign(int sock, Buffer *m)
|
mm_answer_sign(int sock, Buffer *m)
|
||||||
{
|
{
|
||||||
|
struct ssh *ssh = active_state; /* XXX */
|
||||||
extern int auth_sock; /* XXX move to state struct? */
|
extern int auth_sock; /* XXX move to state struct? */
|
||||||
struct sshkey *key;
|
struct sshkey *key;
|
||||||
|
struct sshbuf *sigbuf;
|
||||||
u_char *p;
|
u_char *p;
|
||||||
u_char *signature;
|
u_char *signature;
|
||||||
size_t datlen, siglen;
|
size_t datlen, siglen;
|
||||||
int r, keyid;
|
int r, keyid, is_proof = 0;
|
||||||
|
const char proof_req[] = "hostkeys-prove@openssh.com";
|
||||||
|
|
||||||
debug3("%s", __func__);
|
debug3("%s", __func__);
|
||||||
|
|
||||||
@ -701,9 +704,38 @@ mm_answer_sign(int sock, Buffer *m)
|
|||||||
/*
|
/*
|
||||||
* Supported KEX types use SHA1 (20 bytes), SHA256 (32 bytes),
|
* Supported KEX types use SHA1 (20 bytes), SHA256 (32 bytes),
|
||||||
* SHA384 (48 bytes) and SHA512 (64 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)
|
if (datlen != 20 && datlen != 32 && datlen != 48 && datlen != 64) {
|
||||||
fatal("%s: data length incorrect: %zu", __func__, datlen);
|
/*
|
||||||
|
* 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 */
|
/* save session id, it will be passed on the first call */
|
||||||
if (session_id2_len == 0) {
|
if (session_id2_len == 0) {
|
||||||
@ -717,7 +749,7 @@ mm_answer_sign(int sock, Buffer *m)
|
|||||||
datafellows)) != 0)
|
datafellows)) != 0)
|
||||||
fatal("%s: sshkey_sign failed: %s",
|
fatal("%s: sshkey_sign failed: %s",
|
||||||
__func__, ssh_err(r));
|
__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) {
|
auth_sock > 0) {
|
||||||
if ((r = ssh_agent_sign(auth_sock, key, &signature, &siglen,
|
if ((r = ssh_agent_sign(auth_sock, key, &signature, &siglen,
|
||||||
p, datlen, datafellows)) != 0) {
|
p, datlen, datafellows)) != 0) {
|
||||||
@ -727,7 +759,8 @@ mm_answer_sign(int sock, Buffer *m)
|
|||||||
} else
|
} else
|
||||||
fatal("%s: no hostkey from index %d", __func__, keyid);
|
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);
|
sshbuf_reset(m);
|
||||||
if ((r = sshbuf_put_string(m, signature, siglen)) != 0)
|
if ((r = sshbuf_put_string(m, signature, siglen)) != 0)
|
||||||
|
@ -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 Niels Provos <provos@citi.umich.edu>
|
||||||
* Copyright 2002 Markus Friedl <markus@openbsd.org>
|
* Copyright 2002 Markus Friedl <markus@openbsd.org>
|
||||||
@ -219,7 +219,8 @@ mm_choose_dh(int min, int nbits, int max)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
int
|
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;
|
struct kex *kex = *pmonitor->m_pkex;
|
||||||
Buffer m;
|
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__);
|
debug3("%s entering", __func__);
|
||||||
|
|
||||||
buffer_init(&m);
|
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);
|
buffer_put_string(&m, data, datalen);
|
||||||
|
|
||||||
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_SIGN, &m);
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_SIGN, &m);
|
||||||
|
@ -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>
|
* Copyright 2002 Niels Provos <provos@citi.umich.edu>
|
||||||
@ -40,7 +40,7 @@ struct Authctxt;
|
|||||||
void mm_log_handler(LogLevel, const char *, void *);
|
void mm_log_handler(LogLevel, const char *, void *);
|
||||||
int mm_is_monitor(void);
|
int mm_is_monitor(void);
|
||||||
DH *mm_choose_dh(int, int, int);
|
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 *);
|
void mm_inform_authserv(char *, char *);
|
||||||
struct passwd *mm_getpwnamallow(const char *);
|
struct passwd *mm_getpwnamallow(const char *);
|
||||||
char *mm_auth2_read_banner(void);
|
char *mm_auth2_read_banner(void);
|
||||||
|
@ -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>
|
* 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
|
||||||
@ -1480,7 +1480,8 @@ parse_int:
|
|||||||
|
|
||||||
case oUpdateHostkeys:
|
case oUpdateHostkeys:
|
||||||
intptr = &options->update_hostkeys;
|
intptr = &options->update_hostkeys;
|
||||||
goto parse_flag;
|
multistate_ptr = multistate_yesnoask;
|
||||||
|
goto parse_multistate;
|
||||||
|
|
||||||
case oHostbasedKeyTypes:
|
case oHostbasedKeyTypes:
|
||||||
charptr = &options->hostbased_key_types;
|
charptr = &options->hostbased_key_types;
|
||||||
@ -2107,6 +2108,7 @@ fmt_intarg(OpCodes code, int val)
|
|||||||
return fmt_multistate_int(val, multistate_addressfamily);
|
return fmt_multistate_int(val, multistate_addressfamily);
|
||||||
case oVerifyHostKeyDNS:
|
case oVerifyHostKeyDNS:
|
||||||
case oStrictHostKeyChecking:
|
case oStrictHostKeyChecking:
|
||||||
|
case oUpdateHostkeys:
|
||||||
return fmt_multistate_int(val, multistate_yesnoask);
|
return fmt_multistate_int(val, multistate_yesnoask);
|
||||||
case oControlMaster:
|
case oControlMaster:
|
||||||
return fmt_multistate_int(val, multistate_controlmaster);
|
return fmt_multistate_int(val, multistate_controlmaster);
|
||||||
|
@ -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>
|
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||||
@ -148,7 +148,7 @@ typedef struct {
|
|||||||
|
|
||||||
int fingerprint_hash;
|
int fingerprint_hash;
|
||||||
|
|
||||||
int update_hostkeys;
|
int update_hostkeys; /* one of SSH_UPDATE_HOSTKEYS_* */
|
||||||
|
|
||||||
char *hostbased_key_types;
|
char *hostbased_key_types;
|
||||||
|
|
||||||
@ -174,6 +174,10 @@ typedef struct {
|
|||||||
#define SSHCONF_USERCONF 2 /* user provided config file not system */
|
#define SSHCONF_USERCONF 2 /* user provided config file not system */
|
||||||
#define SSHCONF_POSTCANON 4 /* After hostname canonicalisation */
|
#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 initialize_options(Options *);
|
||||||
void fill_default_options(Options *);
|
void fill_default_options(Options *);
|
||||||
void fill_default_options_for_canonicalization(Options *);
|
void fill_default_options_for_canonicalization(Options *);
|
||||||
|
88
serverloop.c
88
serverloop.c
@ -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>
|
* 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
|
||||||
@ -79,6 +79,7 @@
|
|||||||
#include "auth-options.h"
|
#include "auth-options.h"
|
||||||
#include "serverloop.h"
|
#include "serverloop.h"
|
||||||
#include "roaming.h"
|
#include "roaming.h"
|
||||||
|
#include "ssherr.h"
|
||||||
|
|
||||||
extern ServerOptions options;
|
extern ServerOptions options;
|
||||||
|
|
||||||
@ -1149,12 +1150,83 @@ server_input_channel_open(int type, u_int32_t seq, void *ctxt)
|
|||||||
return 0;
|
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
|
static int
|
||||||
server_input_global_request(int type, u_int32_t seq, void *ctxt)
|
server_input_global_request(int type, u_int32_t seq, void *ctxt)
|
||||||
{
|
{
|
||||||
char *rtype;
|
char *rtype;
|
||||||
int want_reply;
|
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);
|
rtype = packet_get_string(NULL);
|
||||||
want_reply = packet_get_char();
|
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);
|
&allocated_listen_port, &options.fwd_opts);
|
||||||
}
|
}
|
||||||
free(fwd.listen_host);
|
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) {
|
} else if (strcmp(rtype, "cancel-tcpip-forward") == 0) {
|
||||||
struct Forward fwd;
|
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) {
|
} else if (strcmp(rtype, "no-more-sessions@openssh.com") == 0) {
|
||||||
no_more_sessions = 1;
|
no_more_sessions = 1;
|
||||||
success = 1;
|
success = 1;
|
||||||
|
} else if (strcmp(rtype, "hostkeys-prove@openssh.com") == 0) {
|
||||||
|
success = server_input_hostkeys_prove(&resp);
|
||||||
}
|
}
|
||||||
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);
|
||||||
if (success && allocated_listen_port > 0)
|
if (success && resp != NULL)
|
||||||
packet_put_int(allocated_listen_port);
|
ssh_packet_put_raw(active_state, sshbuf_ptr(resp),
|
||||||
|
sshbuf_len(resp));
|
||||||
packet_send();
|
packet_send();
|
||||||
packet_write_wait();
|
packet_write_wait();
|
||||||
}
|
}
|
||||||
free(rtype);
|
free(rtype);
|
||||||
|
sshbuf_free(resp);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
* 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_public_key(int, int, struct ssh *);
|
||||||
struct sshkey *_ssh_host_private_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 **,
|
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.
|
* stubs for the server side implementation of kex.
|
||||||
@ -524,7 +524,8 @@ _ssh_order_hostkeyalgs(struct ssh *ssh)
|
|||||||
|
|
||||||
int
|
int
|
||||||
_ssh_host_key_sign(struct sshkey *privkey, struct sshkey *pubkey,
|
_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);
|
return sshkey_sign(privkey, signature, slen, data, dlen, compat);
|
||||||
}
|
}
|
||||||
|
15
ssh_config.5
15
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.203 2015/02/02 07:41:40 djm Exp $
|
.\" $OpenBSD: ssh_config.5,v 1.204 2015/02/16 22:13:32 djm Exp $
|
||||||
.Dd $Mdocdate: February 2 2015 $
|
.Dd $Mdocdate: February 16 2015 $
|
||||||
.Dt SSH_CONFIG 5
|
.Dt SSH_CONFIG 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@ -1510,15 +1510,20 @@ should accept notifications of additional hostkeys from the server sent
|
|||||||
after authentication has completed and add them to
|
after authentication has completed and add them to
|
||||||
.Cm UserKnownHostsFile .
|
.Cm UserKnownHostsFile .
|
||||||
The argument must be
|
The argument must be
|
||||||
.Dq yes
|
.Dq yes ,
|
||||||
or
|
|
||||||
.Dq no
|
.Dq no
|
||||||
(the default).
|
(the default) or
|
||||||
|
.Dq ask .
|
||||||
Enabling this option allows learning alternate hostkeys for a server
|
Enabling this option allows learning alternate hostkeys for a server
|
||||||
and supports graceful key rotation by allowing a server to send replacement
|
and supports graceful key rotation by allowing a server to send replacement
|
||||||
public keys before old ones are removed.
|
public keys before old ones are removed.
|
||||||
Additional hostkeys are only accepted if the key used to authenticate the
|
Additional hostkeys are only accepted if the key used to authenticate the
|
||||||
host was already trusted or explicity accepted by the user.
|
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
|
.Pp
|
||||||
Presently, only
|
Presently, only
|
||||||
.Xr sshd 8
|
.Xr sshd 8
|
||||||
|
35
sshd.c
35
sshd.c
@ -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>
|
* 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
|
||||||
@ -894,18 +894,25 @@ get_hostkey_public_by_index(int ind, struct ssh *ssh)
|
|||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
get_hostkey_index(Key *key, struct ssh *ssh)
|
get_hostkey_index(Key *key, int compare, struct ssh *ssh)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < options.num_host_key_files; i++) {
|
for (i = 0; i < options.num_host_key_files; i++) {
|
||||||
if (key_is_cert(key)) {
|
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);
|
return (i);
|
||||||
} else {
|
} 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);
|
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);
|
return (i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -933,19 +940,23 @@ notify_hostkeys(struct ssh *ssh)
|
|||||||
debug3("%s: key %d: %s %s", __func__, i,
|
debug3("%s: key %d: %s %s", __func__, i,
|
||||||
sshkey_ssh_name(key), fp);
|
sshkey_ssh_name(key), fp);
|
||||||
free(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",
|
fatal("%s: couldn't put hostkey %d: %s",
|
||||||
__func__, i, ssh_err(r));
|
__func__, i, ssh_err(r));
|
||||||
|
packet_put_string(sshbuf_ptr(buf), sshbuf_len(buf));
|
||||||
nkeys++;
|
nkeys++;
|
||||||
}
|
}
|
||||||
|
debug3("%s: sent %d hostkeys", __func__, nkeys);
|
||||||
if (nkeys == 0)
|
if (nkeys == 0)
|
||||||
fatal("%s: no hostkeys", __func__);
|
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();
|
packet_send();
|
||||||
|
sshbuf_free(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -2484,7 +2495,7 @@ do_ssh1_kex(void)
|
|||||||
|
|
||||||
int
|
int
|
||||||
sshd_hostkey_sign(Key *privkey, Key *pubkey, u_char **signature, size_t *slen,
|
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;
|
int r;
|
||||||
u_int xxx_slen, xxx_dlen = dlen;
|
u_int xxx_slen, xxx_dlen = dlen;
|
||||||
|
4
ssherr.c
4
ssherr.c
@ -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
|
* Copyright (c) 2011 Damien Miller
|
||||||
*
|
*
|
||||||
@ -121,6 +121,8 @@ ssh_err(int n)
|
|||||||
return "agent not present";
|
return "agent not present";
|
||||||
case SSH_ERR_AGENT_NO_IDENTITIES:
|
case SSH_ERR_AGENT_NO_IDENTITIES:
|
||||||
return "agent contains 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:
|
case SSH_ERR_KRL_BAD_MAGIC:
|
||||||
return "KRL file has invalid magic number";
|
return "KRL file has invalid magic number";
|
||||||
case SSH_ERR_KEY_REVOKED:
|
case SSH_ERR_KEY_REVOKED:
|
||||||
|
Loading…
Reference in New Issue
Block a user