summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordjm@openbsd.org <djm@openbsd.org>2015-02-16 22:13:32 +0000
committerDamien Miller <djm@mindrot.org>2015-02-17 09:32:32 +1100
commit523463a3a2a9bfc6cfc5afa01bae9147f76a37cc (patch)
tree772be92cee9553c19d51b4570113c3d4de0c2d8b
parent6c5c949782d86a6e7d58006599c7685bfcd01685 (diff)
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@
-rw-r--r--PROTOCOL53
-rw-r--r--auth.h7
-rw-r--r--clientloop.c353
-rw-r--r--kex.h6
-rw-r--r--monitor.c45
-rw-r--r--monitor_wrap.c7
-rw-r--r--monitor_wrap.h4
-rw-r--r--readconf.c6
-rw-r--r--readconf.h8
-rw-r--r--serverloop.c88
-rw-r--r--ssh_api.c7
-rw-r--r--ssh_config.515
-rw-r--r--sshd.c35
-rw-r--r--ssherr.c4
14 files changed, 537 insertions, 101 deletions
diff --git a/PROTOCOL b/PROTOCOL
index 8150c577b..f9560839e 100644
--- a/PROTOCOL
+++ b/PROTOCOL
@@ -40,8 +40,8 @@ http://www.openssh.com/txt/draft-miller-secsh-compression-delayed-00.txt
40 "ecdsa-sha2-nistp521-cert-v01@openssh.com" 40 "ecdsa-sha2-nistp521-cert-v01@openssh.com"
41 41
42OpenSSH introduces new public key algorithms to support certificate 42OpenSSH introduces new public key algorithms to support certificate
43authentication for users and hostkeys. These methods are documented in 43authentication for users and host keys. These methods are documented
44the file PROTOCOL.certkeys 44in the file PROTOCOL.certkeys
45 45
461.4. transport: Elliptic Curve cryptography 461.4. transport: Elliptic Curve cryptography
47 47
@@ -283,26 +283,51 @@ by the client cancel the forwarding of a Unix domain socket.
283 string socket path 283 string socket path
284 284
2852.5. connection: hostkey update and rotation "hostkeys@openssh.com" 2852.5. connection: hostkey update and rotation "hostkeys@openssh.com"
286and "hostkeys-prove@openssh.com"
286 287
287OpenSSH supports a protocol extension allowing a server to inform 288OpenSSH supports a protocol extension allowing a server to inform
288a client of all its protocol v.2 hostkeys after user-authentication 289a client of all its protocol v.2 host keys after user-authentication
289has completed. 290has completed.
290 291
291 byte SSH_MSG_GLOBAL_REQUEST 292 byte SSH_MSG_GLOBAL_REQUEST
292 string "hostkeys@openssh.com" 293 string "hostkeys@openssh.com"
293 string[] hostkeys 294 string[] hostkeys
294 295
295Upon receiving this message, a client may update its known_hosts 296Upon receiving this message, a client should check which of the
296file, adding keys that it has not seen before and deleting keys 297supplied host keys are present in known_hosts. For keys that are
297for the server host that are no longer offered. 298not present, it should send a "hostkeys-prove@openssh.com" message
299to request the server prove ownership of the private half of the
300key.
298 301
299This extension allows a client to learn key types that it had 302 byte SSH_MSG_GLOBAL_REQUEST
300not previously encountered, thereby allowing it to potentially 303 string "hostkeys-prove@openssh.com"
301upgrade from weaker key algorithms to better ones. It also 304 char 1 /* want-reply */
302supports graceful key rotation: a server may offer multiple keys 305 string[] hostkeys
303of the same type for a period (to give clients an opportunity to 306
304learn them using this extension) before removing the deprecated 307When a server receives this message, it should generate a signature
305key from those offered. 308using each requested key over the following:
309
310 string session identifier
311 string "hostkeys-prove@openssh.com"
312 string hostkey
313
314These signatures should be included in the reply, in the order matching
315the hostkeys in the request:
316
317 byte SSH_MSG_REQUEST_SUCCESS
318 string[] signatures
319
320When the client receives this reply (and not a failure), it should
321validate the signatures and may update its known_hosts file, adding keys
322that it has not seen before and deleting keys for the server host that
323are no longer offered.
324
325These extensions let a client learn key types that it had not previously
326encountered, thereby allowing it to potentially upgrade from weaker
327key algorithms to better ones. It also supports graceful key rotation:
328a server may offer multiple keys of the same type for a period (to
329give clients an opportunity to learn them using this extension) before
330removing the deprecated key from those offered.
306 331
3073. SFTP protocol changes 3323. SFTP protocol changes
308 333
@@ -428,4 +453,4 @@ respond with a SSH_FXP_STATUS message.
428This extension is advertised in the SSH_FXP_VERSION hello with version 453This extension is advertised in the SSH_FXP_VERSION hello with version
429"1". 454"1".
430 455
431$OpenBSD: PROTOCOL,v 1.25 2015/01/26 03:04:45 djm Exp $ 456$OpenBSD: PROTOCOL,v 1.26 2015/02/16 22:13:32 djm Exp $
diff --git a/auth.h b/auth.h
index d28261929..db8603760 100644
--- a/auth.h
+++ b/auth.h
@@ -1,4 +1,4 @@
1/* $OpenBSD: auth.h,v 1.81 2015/01/26 06:10:03 djm Exp $ */ 1/* $OpenBSD: auth.h,v 1.82 2015/02/16 22:13:32 djm Exp $ */
2 2
3/* 3/*
4 * Copyright (c) 2000 Markus Friedl. All rights reserved. 4 * Copyright (c) 2000 Markus Friedl. All rights reserved.
@@ -206,9 +206,10 @@ Key *get_hostkey_by_index(int);
206Key *get_hostkey_public_by_index(int, struct ssh *); 206Key *get_hostkey_public_by_index(int, struct ssh *);
207Key *get_hostkey_public_by_type(int, int, struct ssh *); 207Key *get_hostkey_public_by_type(int, int, struct ssh *);
208Key *get_hostkey_private_by_type(int, int, struct ssh *); 208Key *get_hostkey_private_by_type(int, int, struct ssh *);
209int get_hostkey_index(Key *, struct ssh *); 209int get_hostkey_index(Key *, int, struct ssh *);
210int ssh1_session_key(BIGNUM *); 210int ssh1_session_key(BIGNUM *);
211int sshd_hostkey_sign(Key *, Key *, u_char **, size_t *, u_char *, size_t, u_int); 211int sshd_hostkey_sign(Key *, Key *, u_char **, size_t *,
212 const u_char *, size_t, u_int);
212 213
213/* debug messages during authentication */ 214/* debug messages during authentication */
214void auth_debug_add(const char *fmt,...) __attribute__((format(printf, 1, 2))); 215void auth_debug_add(const char *fmt,...) __attribute__((format(printf, 1, 2)));
diff --git a/clientloop.c b/clientloop.c
index c6f8e9dc1..a19d9d06f 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: clientloop.c,v 1.268 2015/02/16 22:08:57 djm Exp $ */ 1/* $OpenBSD: clientloop.c,v 1.269 2015/02/16 22:13:32 djm Exp $ */
2/* 2/*
3 * Author: Tatu Ylonen <ylo@cs.hut.fi> 3 * Author: Tatu Ylonen <ylo@cs.hut.fi>
4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -2089,6 +2089,216 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt)
2089 return 0; 2089 return 0;
2090} 2090}
2091 2091
2092struct hostkeys_update_ctx {
2093 /* The hostname and (optionally) IP address string for the server */
2094 char *host_str, *ip_str;
2095
2096 /*
2097 * Keys received from the server and a flag for each indicating
2098 * whether they already exist in known_hosts.
2099 * keys_seen is filled in by hostkeys_find() and later (for new
2100 * keys) by client_global_hostkeys_private_confirm().
2101 */
2102 struct sshkey **keys;
2103 int *keys_seen;
2104 size_t nkeys;
2105
2106 size_t nnew;
2107
2108 /*
2109 * Keys that are in known_hosts, but were not present in the update
2110 * from the server (i.e. scheduled to be deleted).
2111 * Filled in by hostkeys_find().
2112 */
2113 struct sshkey **old_keys;
2114 size_t nold;
2115};
2116
2117static void
2118hostkeys_update_ctx_free(struct hostkeys_update_ctx *ctx)
2119{
2120 size_t i;
2121
2122 if (ctx == NULL)
2123 return;
2124 for (i = 0; i < ctx->nkeys; i++)
2125 sshkey_free(ctx->keys[i]);
2126 free(ctx->keys);
2127 free(ctx->keys_seen);
2128 for (i = 0; i < ctx->nold; i++)
2129 sshkey_free(ctx->old_keys[i]);
2130 free(ctx->old_keys);
2131 free(ctx->host_str);
2132 free(ctx->ip_str);
2133 free(ctx);
2134}
2135
2136static int
2137hostkeys_find(struct hostkey_foreach_line *l, void *_ctx)
2138{
2139 struct hostkeys_update_ctx *ctx = (struct hostkeys_update_ctx *)_ctx;
2140 size_t i;
2141 struct sshkey **tmp;
2142
2143 if (l->status != HKF_STATUS_MATCHED || l->key == NULL ||
2144 l->key->type == KEY_RSA1)
2145 return 0;
2146
2147 /* Mark off keys we've already seen for this host */
2148 for (i = 0; i < ctx->nkeys; i++) {
2149 if (sshkey_equal(l->key, ctx->keys[i])) {
2150 debug3("%s: found %s key at %s:%ld", __func__,
2151 sshkey_ssh_name(ctx->keys[i]), l->path, l->linenum);
2152 ctx->keys_seen[i] = 1;
2153 return 0;
2154 }
2155 }
2156 /* This line contained a key that not offered by the server */
2157 debug3("%s: deprecated %s key at %s:%ld", __func__,
2158 sshkey_ssh_name(l->key), l->path, l->linenum);
2159 if ((tmp = reallocarray(ctx->old_keys, ctx->nold + 1,
2160 sizeof(*ctx->old_keys))) == NULL)
2161 fatal("%s: reallocarray failed nold = %zu",
2162 __func__, ctx->nold);
2163 ctx->old_keys = tmp;
2164 ctx->old_keys[ctx->nold++] = l->key;
2165 l->key = NULL;
2166
2167 return 0;
2168}
2169
2170static void
2171update_known_hosts(struct hostkeys_update_ctx *ctx)
2172{
2173 int r, loglevel = options.update_hostkeys == SSH_UPDATE_HOSTKEYS_ASK ?
2174 SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_VERBOSE;
2175 char *fp, *response;
2176 size_t i;
2177
2178 for (i = 0; i < ctx->nkeys; i++) {
2179 if (ctx->keys_seen[i] != 2)
2180 continue;
2181 if ((fp = sshkey_fingerprint(ctx->keys[i],
2182 options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
2183 fatal("%s: sshkey_fingerprint failed", __func__);
2184 do_log2(loglevel, "Learned new hostkey: %s %s",
2185 sshkey_type(ctx->keys[i]), fp);
2186 free(fp);
2187 }
2188 for (i = 0; i < ctx->nold; i++) {
2189 if ((fp = sshkey_fingerprint(ctx->old_keys[i],
2190 options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
2191 fatal("%s: sshkey_fingerprint failed", __func__);
2192 do_log2(loglevel, "Deprecating obsolete hostkey: %s %s",
2193 sshkey_type(ctx->old_keys[i]), fp);
2194 free(fp);
2195 }
2196 if (options.update_hostkeys == SSH_UPDATE_HOSTKEYS_ASK) {
2197 leave_raw_mode(options.request_tty == REQUEST_TTY_FORCE);
2198 response = NULL;
2199 for (i = 0; !quit_pending && i < 3; i++) {
2200 free(response);
2201 response = read_passphrase("Accept updated hostkeys? "
2202 "(yes/no): ", RP_ECHO);
2203 if (strcasecmp(response, "yes") == 0)
2204 break;
2205 else if (quit_pending || response == NULL ||
2206 strcasecmp(response, "no") == 0) {
2207 options.update_hostkeys = 0;
2208 break;
2209 } else {
2210 do_log2(loglevel, "Please enter "
2211 "\"yes\" or \"no\"");
2212 }
2213 }
2214 if (quit_pending || i >= 3 || response == NULL)
2215 options.update_hostkeys = 0;
2216 free(response);
2217 enter_raw_mode(options.request_tty == REQUEST_TTY_FORCE);
2218 }
2219
2220 /*
2221 * Now that all the keys are verified, we can go ahead and replace
2222 * them in known_hosts (assuming SSH_UPDATE_HOSTKEYS_ASK didn't
2223 * cancel the operation).
2224 */
2225 if (options.update_hostkeys != 0 &&
2226 (r = hostfile_replace_entries(options.user_hostfiles[0],
2227 ctx->host_str, ctx->ip_str, ctx->keys, ctx->nkeys,
2228 options.hash_known_hosts, 0,
2229 options.fingerprint_hash)) != 0)
2230 error("%s: hostfile_replace_entries failed: %s",
2231 __func__, ssh_err(r));
2232}
2233
2234static void
2235client_global_hostkeys_private_confirm(int type, u_int32_t seq, void *_ctx)
2236{
2237 struct ssh *ssh = active_state; /* XXX */
2238 struct hostkeys_update_ctx *ctx = (struct hostkeys_update_ctx *)_ctx;
2239 size_t i, ndone;
2240 struct sshbuf *signdata;
2241 int r;
2242 const u_char *sig;
2243 size_t siglen;
2244
2245 if (ctx->nnew == 0)
2246 fatal("%s: ctx->nnew == 0", __func__); /* sanity */
2247 if (type != SSH2_MSG_REQUEST_SUCCESS) {
2248 error("Server failed to confirm ownership of "
2249 "private host keys");
2250 hostkeys_update_ctx_free(ctx);
2251 return;
2252 }
2253 if ((signdata = sshbuf_new()) == NULL)
2254 fatal("%s: sshbuf_new failed", __func__);
2255 /* Don't want to accidentally accept an unbound signature */
2256 if (ssh->kex->session_id_len == 0)
2257 fatal("%s: ssh->kex->session_id_len == 0", __func__);
2258 /*
2259 * Expect a signature for each of the ctx->nnew private keys we
2260 * haven't seen before. They will be in the same order as the
2261 * ctx->keys where the corresponding ctx->keys_seen[i] == 0.
2262 */
2263 for (ndone = i = 0; i < ctx->nkeys; i++) {
2264 if (ctx->keys_seen[i])
2265 continue;
2266 /* Prepare data to be signed: session ID, unique string, key */
2267 sshbuf_reset(signdata);
2268 if ((r = sshbuf_put_string(signdata, ssh->kex->session_id,
2269 ssh->kex->session_id_len)) != 0 ||
2270 (r = sshbuf_put_cstring(signdata,
2271 "hostkeys-prove@openssh.com")) != 0 ||
2272 (r = sshkey_puts(ctx->keys[i], signdata)) != 0)
2273 fatal("%s: failed to prepare signature: %s",
2274 __func__, ssh_err(r));
2275 /* Extract and verify signature */
2276 if ((r = sshpkt_get_string_direct(ssh, &sig, &siglen)) != 0) {
2277 error("%s: couldn't parse message: %s",
2278 __func__, ssh_err(r));
2279 goto out;
2280 }
2281 if ((r = sshkey_verify(ctx->keys[i], sig, siglen,
2282 sshbuf_ptr(signdata), sshbuf_len(signdata), 0)) != 0) {
2283 error("%s: server gave bad signature for %s key %zu",
2284 __func__, sshkey_type(ctx->keys[i]), i);
2285 goto out;
2286 }
2287 /* Key is good. Mark it as 'seen' */
2288 ctx->keys_seen[i] = 2;
2289 ndone++;
2290 }
2291 if (ndone != ctx->nnew)
2292 fatal("%s: ndone != ctx->nnew (%zu / %zu)", __func__,
2293 ndone, ctx->nnew); /* Shouldn't happen */
2294 ssh_packet_check_eom(ssh);
2295
2296 /* Make the edits to known_hosts */
2297 update_known_hosts(ctx);
2298 out:
2299 hostkeys_update_ctx_free(ctx);
2300}
2301
2092/* 2302/*
2093 * Handle hostkeys@openssh.com global request to inform the client of all 2303 * Handle hostkeys@openssh.com global request to inform the client of all
2094 * the server's hostkeys. The keys are checked against the user's 2304 * 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)
2097static int 2307static int
2098client_input_hostkeys(void) 2308client_input_hostkeys(void)
2099{ 2309{
2310 struct ssh *ssh = active_state; /* XXX */
2100 const u_char *blob = NULL; 2311 const u_char *blob = NULL;
2101 u_int i, len = 0, nkeys = 0; 2312 size_t i, len = 0;
2102 struct sshbuf *buf = NULL; 2313 struct sshbuf *buf = NULL;
2103 struct sshkey *key = NULL, **tmp, **keys = NULL; 2314 struct sshkey *key = NULL, **tmp;
2104 int r, success = 1; 2315 int r;
2105 char *fp, *host_str = NULL, *ip_str = NULL; 2316 char *fp;
2106 static int hostkeys_seen = 0; /* XXX use struct ssh */ 2317 static int hostkeys_seen = 0; /* XXX use struct ssh */
2107 extern struct sockaddr_storage hostaddr; /* XXX from ssh.c */ 2318 extern struct sockaddr_storage hostaddr; /* XXX from ssh.c */
2319 struct hostkeys_update_ctx *ctx;
2108 2320
2109 /* 2321 ctx = xcalloc(1, sizeof(*ctx));
2110 * NB. Return success for all cases other than protocol error. The
2111 * server doesn't need to know what the client does with its hosts
2112 * file.
2113 */
2114
2115 blob = packet_get_string_ptr(&len);
2116 packet_check_eom();
2117 2322
2118 if (hostkeys_seen) 2323 if (hostkeys_seen)
2119 fatal("%s: server already sent hostkeys", __func__); 2324 fatal("%s: server already sent hostkeys", __func__);
2325 if (options.update_hostkeys == SSH_UPDATE_HOSTKEYS_ASK &&
2326 options.batch_mode)
2327 return 1; /* won't ask in batchmode, so don't even try */
2120 if (!options.update_hostkeys || options.num_user_hostfiles <= 0) 2328 if (!options.update_hostkeys || options.num_user_hostfiles <= 0)
2121 return 1; 2329 return 1;
2122 if ((buf = sshbuf_from(blob, len)) == NULL) 2330 while (ssh_packet_remaining(ssh) > 0) {
2123 fatal("%s: sshbuf_from failed", __func__);
2124 while (sshbuf_len(buf) > 0) {
2125 sshkey_free(key); 2331 sshkey_free(key);
2126 key = NULL; 2332 key = NULL;
2127 if ((r = sshkey_froms(buf, &key)) != 0) 2333 if ((r = sshpkt_get_string_direct(ssh, &blob, &len)) != 0) {
2334 error("%s: couldn't parse message: %s",
2335 __func__, ssh_err(r));
2336 goto out;
2337 }
2338 if ((r = sshkey_from_blob(blob, len, &key)) != 0)
2128 fatal("%s: parse key: %s", __func__, ssh_err(r)); 2339 fatal("%s: parse key: %s", __func__, ssh_err(r));
2129 fp = sshkey_fingerprint(key, options.fingerprint_hash, 2340 fp = sshkey_fingerprint(key, options.fingerprint_hash,
2130 SSH_FP_DEFAULT); 2341 SSH_FP_DEFAULT);
@@ -2140,47 +2351,107 @@ client_input_hostkeys(void)
2140 __func__, sshkey_ssh_name(key)); 2351 __func__, sshkey_ssh_name(key));
2141 continue; 2352 continue;
2142 } 2353 }
2143 if ((tmp = reallocarray(keys, nkeys + 1, 2354 /* Skip certs */
2144 sizeof(*keys))) == NULL) 2355 if (sshkey_is_cert(key)) {
2145 fatal("%s: reallocarray failed nkeys = %u", 2356 debug3("%s: %s key is a certificate; skipping",
2146 __func__, nkeys); 2357 __func__, sshkey_ssh_name(key));
2147 keys = tmp; 2358 continue;
2148 keys[nkeys++] = key; 2359 }
2360 /* Ensure keys are unique */
2361 for (i = 0; i < ctx->nkeys; i++) {
2362 if (sshkey_equal(key, ctx->keys[i])) {
2363 error("%s: received duplicated %s host key",
2364 __func__, sshkey_ssh_name(key));
2365 goto out;
2366 }
2367 }
2368 /* Key is good, record it */
2369 if ((tmp = reallocarray(ctx->keys, ctx->nkeys + 1,
2370 sizeof(*ctx->keys))) == NULL)
2371 fatal("%s: reallocarray failed nkeys = %zu",
2372 __func__, ctx->nkeys);
2373 ctx->keys = tmp;
2374 ctx->keys[ctx->nkeys++] = key;
2149 key = NULL; 2375 key = NULL;
2150 } 2376 }
2151 2377
2152 if (nkeys == 0) { 2378 if (ctx->nkeys == 0) {
2153 error("%s: server sent no hostkeys", __func__); 2379 error("%s: server sent no hostkeys", __func__);
2154 goto out; 2380 goto out;
2155 } 2381 }
2382 if ((ctx->keys_seen = calloc(ctx->nkeys,
2383 sizeof(*ctx->keys_seen))) == NULL)
2384 fatal("%s: calloc failed", __func__);
2156 2385
2157 get_hostfile_hostname_ipaddr(host, 2386 get_hostfile_hostname_ipaddr(host,
2158 options.check_host_ip ? (struct sockaddr *)&hostaddr : NULL, 2387 options.check_host_ip ? (struct sockaddr *)&hostaddr : NULL,
2159 options.port, &host_str, options.check_host_ip ? &ip_str : NULL); 2388 options.port, &ctx->host_str,
2389 options.check_host_ip ? &ctx->ip_str : NULL);
2390
2391 /* Find which keys we already know about. */
2392 if ((r = hostkeys_foreach(options.user_hostfiles[0], hostkeys_find,
2393 ctx, ctx->host_str, ctx->ip_str,
2394 HKF_WANT_PARSE_KEY|HKF_WANT_MATCH)) != 0) {
2395 error("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r));
2396 goto out;
2397 }
2398
2399 /* Figure out if we have any new keys to add */
2400 ctx->nnew = 0;
2401 for (i = 0; i < ctx->nkeys; i++) {
2402 if (!ctx->keys_seen[i])
2403 ctx->nnew++;
2404 }
2160 2405
2161 debug3("%s: update known hosts for %s%s%s with %u keys from server", 2406 debug3("%s: %zu keys from server: %zu new, %zu retained. %zu to remove",
2162 __func__, host_str, 2407 __func__, ctx->nkeys, ctx->nnew, ctx->nkeys - ctx->nnew, ctx->nold);
2163 options.check_host_ip ? " " : "",
2164 options.check_host_ip ? ip_str : "", nkeys);
2165 2408
2166 if ((r = hostfile_replace_entries(options.user_hostfiles[0], 2409 if (ctx->nnew == 0 && ctx->nold != 0) {
2167 host_str, options.check_host_ip ? ip_str : NULL, 2410 /* We have some keys to remove. Just do it. */
2168 keys, nkeys, options.hash_known_hosts, 0, 2411 update_known_hosts(ctx);
2169 options.fingerprint_hash)) != 0) { 2412 } else if (ctx->nnew != 0) {
2170 error("%s: hostfile_replace_entries failed: %s", 2413 /*
2171 __func__, ssh_err(r)); 2414 * We have received hitherto-unseen keys from the server.
2172 goto out; 2415 * Ask the server to confirm ownership of the private halves.
2416 */
2417 debug3("%s: asking server to prove ownership for %zu keys",
2418 __func__, ctx->nnew);
2419 if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 ||
2420 (r = sshpkt_put_cstring(ssh,
2421 "hostkeys-prove@openssh.com")) != 0 ||
2422 (r = sshpkt_put_u8(ssh, 1)) != 0) /* bool: want reply */
2423 fatal("%s: cannot prepare packet: %s",
2424 __func__, ssh_err(r));
2425 if ((buf = sshbuf_new()) == NULL)
2426 fatal("%s: sshbuf_new", __func__);
2427 for (i = 0; i < ctx->nkeys; i++) {
2428 if (ctx->keys_seen[i])
2429 continue;
2430 sshbuf_reset(buf);
2431 if ((r = sshkey_putb(ctx->keys[i], buf)) != 0)
2432 fatal("%s: sshkey_putb: %s",
2433 __func__, ssh_err(r));
2434 if ((r = sshpkt_put_stringb(ssh, buf)) != 0)
2435 fatal("%s: sshpkt_put_string: %s",
2436 __func__, ssh_err(r));
2437 }
2438 if ((r = sshpkt_send(ssh)) != 0)
2439 fatal("%s: sshpkt_send: %s", __func__, ssh_err(r));
2440 client_register_global_confirm(
2441 client_global_hostkeys_private_confirm, ctx);
2442 ctx = NULL; /* will be freed in callback */
2173 } 2443 }
2174 2444
2175 /* Success */ 2445 /* Success */
2176 out: 2446 out:
2177 free(host_str); 2447 hostkeys_update_ctx_free(ctx);
2178 free(ip_str);
2179 sshkey_free(key); 2448 sshkey_free(key);
2180 for (i = 0; i < nkeys; i++)
2181 sshkey_free(keys[i]);
2182 sshbuf_free(buf); 2449 sshbuf_free(buf);
2183 return success; 2450 /*
2451 * NB. Return success for all cases. The server doesn't need to know
2452 * what the client does with its hosts file.
2453 */
2454 return 1;
2184} 2455}
2185 2456
2186static int 2457static int
diff --git a/kex.h b/kex.h
index 45d35773c..99a7d55bf 100644
--- a/kex.h
+++ b/kex.h
@@ -1,4 +1,4 @@
1/* $OpenBSD: kex.h,v 1.70 2015/01/26 06:10:03 djm Exp $ */ 1/* $OpenBSD: kex.h,v 1.71 2015/02/16 22:13:32 djm Exp $ */
2 2
3/* 3/*
4 * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. 4 * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved.
@@ -130,9 +130,9 @@ struct kex {
130 int (*verify_host_key)(struct sshkey *, struct ssh *); 130 int (*verify_host_key)(struct sshkey *, struct ssh *);
131 struct sshkey *(*load_host_public_key)(int, int, struct ssh *); 131 struct sshkey *(*load_host_public_key)(int, int, struct ssh *);
132 struct sshkey *(*load_host_private_key)(int, int, struct ssh *); 132 struct sshkey *(*load_host_private_key)(int, int, struct ssh *);
133 int (*host_key_index)(struct sshkey *, struct ssh *); 133 int (*host_key_index)(struct sshkey *, int, struct ssh *);
134 int (*sign)(struct sshkey *, struct sshkey *, 134 int (*sign)(struct sshkey *, struct sshkey *,
135 u_char **, size_t *, u_char *, size_t, u_int); 135 u_char **, size_t *, const u_char *, size_t, u_int);
136 int (*kex[KEX_MAX])(struct ssh *); 136 int (*kex[KEX_MAX])(struct ssh *);
137 /* kex specific state */ 137 /* kex specific state */
138 DH *dh; /* DH */ 138 DH *dh; /* DH */
diff --git a/monitor.c b/monitor.c
index e97b20ef0..6e97def1c 100644
--- a/monitor.c
+++ b/monitor.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: monitor.c,v 1.143 2015/02/13 18:57:00 markus Exp $ */ 1/* $OpenBSD: monitor.c,v 1.144 2015/02/16 22:13:32 djm Exp $ */
2/* 2/*
3 * Copyright 2002 Niels Provos <provos@citi.umich.edu> 3 * Copyright 2002 Niels Provos <provos@citi.umich.edu>
4 * Copyright 2002 Markus Friedl <markus@openbsd.org> 4 * Copyright 2002 Markus Friedl <markus@openbsd.org>
@@ -685,12 +685,15 @@ mm_answer_moduli(int sock, Buffer *m)
685int 685int
686mm_answer_sign(int sock, Buffer *m) 686mm_answer_sign(int sock, Buffer *m)
687{ 687{
688 struct ssh *ssh = active_state; /* XXX */
688 extern int auth_sock; /* XXX move to state struct? */ 689 extern int auth_sock; /* XXX move to state struct? */
689 struct sshkey *key; 690 struct sshkey *key;
691 struct sshbuf *sigbuf;
690 u_char *p; 692 u_char *p;
691 u_char *signature; 693 u_char *signature;
692 size_t datlen, siglen; 694 size_t datlen, siglen;
693 int r, keyid; 695 int r, keyid, is_proof = 0;
696 const char proof_req[] = "hostkeys-prove@openssh.com";
694 697
695 debug3("%s", __func__); 698 debug3("%s", __func__);
696 699
@@ -701,9 +704,38 @@ mm_answer_sign(int sock, Buffer *m)
701 /* 704 /*
702 * Supported KEX types use SHA1 (20 bytes), SHA256 (32 bytes), 705 * Supported KEX types use SHA1 (20 bytes), SHA256 (32 bytes),
703 * SHA384 (48 bytes) and SHA512 (64 bytes). 706 * SHA384 (48 bytes) and SHA512 (64 bytes).
707 *
708 * Otherwise, verify the signature request is for a hostkey
709 * proof.
710 *
711 * XXX perform similar check for KEX signature requests too?
712 * it's not trivial, since what is signed is the hash, rather
713 * than the full kex structure...
704 */ 714 */
705 if (datlen != 20 && datlen != 32 && datlen != 48 && datlen != 64) 715 if (datlen != 20 && datlen != 32 && datlen != 48 && datlen != 64) {
706 fatal("%s: data length incorrect: %zu", __func__, datlen); 716 /*
717 * Construct expected hostkey proof and compare it to what
718 * the client sent us.
719 */
720 if (session_id2_len == 0) /* hostkeys is never first */
721 fatal("%s: bad data length: %zu", __func__, datlen);
722 if ((key = get_hostkey_public_by_index(keyid, ssh)) == NULL)
723 fatal("%s: no hostkey for index %d", __func__, keyid);
724 if ((sigbuf = sshbuf_new()) == NULL)
725 fatal("%s: sshbuf_new", __func__);
726 if ((r = sshbuf_put_string(sigbuf, session_id2,
727 session_id2_len) != 0) ||
728 (r = sshbuf_put_cstring(sigbuf, proof_req)) != 0 ||
729 (r = sshkey_puts(key, sigbuf)) != 0)
730 fatal("%s: couldn't prepare private key "
731 "proof buffer: %s", __func__, ssh_err(r));
732 if (datlen != sshbuf_len(sigbuf) ||
733 memcmp(p, sshbuf_ptr(sigbuf), sshbuf_len(sigbuf)) != 0)
734 fatal("%s: bad data length: %zu, hostkey proof len %zu",
735 __func__, datlen, sshbuf_len(sigbuf));
736 sshbuf_free(sigbuf);
737 is_proof = 1;
738 }
707 739
708 /* save session id, it will be passed on the first call */ 740 /* save session id, it will be passed on the first call */
709 if (session_id2_len == 0) { 741 if (session_id2_len == 0) {
@@ -717,7 +749,7 @@ mm_answer_sign(int sock, Buffer *m)
717 datafellows)) != 0) 749 datafellows)) != 0)
718 fatal("%s: sshkey_sign failed: %s", 750 fatal("%s: sshkey_sign failed: %s",
719 __func__, ssh_err(r)); 751 __func__, ssh_err(r));
720 } else if ((key = get_hostkey_public_by_index(keyid, active_state)) != NULL && 752 } else if ((key = get_hostkey_public_by_index(keyid, ssh)) != NULL &&
721 auth_sock > 0) { 753 auth_sock > 0) {
722 if ((r = ssh_agent_sign(auth_sock, key, &signature, &siglen, 754 if ((r = ssh_agent_sign(auth_sock, key, &signature, &siglen,
723 p, datlen, datafellows)) != 0) { 755 p, datlen, datafellows)) != 0) {
@@ -727,7 +759,8 @@ mm_answer_sign(int sock, Buffer *m)
727 } else 759 } else
728 fatal("%s: no hostkey from index %d", __func__, keyid); 760 fatal("%s: no hostkey from index %d", __func__, keyid);
729 761
730 debug3("%s: signature %p(%zu)", __func__, signature, siglen); 762 debug3("%s: %s signature %p(%zu)", __func__,
763 is_proof ? "KEX" : "hostkey proof", signature, siglen);
731 764
732 sshbuf_reset(m); 765 sshbuf_reset(m);
733 if ((r = sshbuf_put_string(m, signature, siglen)) != 0) 766 if ((r = sshbuf_put_string(m, signature, siglen)) != 0)
diff --git a/monitor_wrap.c b/monitor_wrap.c
index c0935dc69..b379f0555 100644
--- a/monitor_wrap.c
+++ b/monitor_wrap.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: monitor_wrap.c,v 1.83 2015/01/19 20:16:15 markus Exp $ */ 1/* $OpenBSD: monitor_wrap.c,v 1.84 2015/02/16 22:13:32 djm Exp $ */
2/* 2/*
3 * Copyright 2002 Niels Provos <provos@citi.umich.edu> 3 * Copyright 2002 Niels Provos <provos@citi.umich.edu>
4 * Copyright 2002 Markus Friedl <markus@openbsd.org> 4 * Copyright 2002 Markus Friedl <markus@openbsd.org>
@@ -219,7 +219,8 @@ mm_choose_dh(int min, int nbits, int max)
219#endif 219#endif
220 220
221int 221int
222mm_key_sign(Key *key, u_char **sigp, u_int *lenp, u_char *data, u_int datalen) 222mm_key_sign(Key *key, u_char **sigp, u_int *lenp,
223 const u_char *data, u_int datalen)
223{ 224{
224 struct kex *kex = *pmonitor->m_pkex; 225 struct kex *kex = *pmonitor->m_pkex;
225 Buffer m; 226 Buffer m;
@@ -227,7 +228,7 @@ mm_key_sign(Key *key, u_char **sigp, u_int *lenp, u_char *data, u_int datalen)
227 debug3("%s entering", __func__); 228 debug3("%s entering", __func__);
228 229
229 buffer_init(&m); 230 buffer_init(&m);
230 buffer_put_int(&m, kex->host_key_index(key, active_state)); 231 buffer_put_int(&m, kex->host_key_index(key, 0, active_state));
231 buffer_put_string(&m, data, datalen); 232 buffer_put_string(&m, data, datalen);
232 233
233 mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_SIGN, &m); 234 mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_SIGN, &m);
diff --git a/monitor_wrap.h b/monitor_wrap.h
index d97e8db1e..e18784ac4 100644
--- a/monitor_wrap.h
+++ b/monitor_wrap.h
@@ -1,4 +1,4 @@
1/* $OpenBSD: monitor_wrap.h,v 1.25 2015/01/19 19:52:16 markus Exp $ */ 1/* $OpenBSD: monitor_wrap.h,v 1.26 2015/02/16 22:13:32 djm Exp $ */
2 2
3/* 3/*
4 * Copyright 2002 Niels Provos <provos@citi.umich.edu> 4 * Copyright 2002 Niels Provos <provos@citi.umich.edu>
@@ -40,7 +40,7 @@ struct Authctxt;
40void mm_log_handler(LogLevel, const char *, void *); 40void mm_log_handler(LogLevel, const char *, void *);
41int mm_is_monitor(void); 41int mm_is_monitor(void);
42DH *mm_choose_dh(int, int, int); 42DH *mm_choose_dh(int, int, int);
43int mm_key_sign(Key *, u_char **, u_int *, u_char *, u_int); 43int mm_key_sign(Key *, u_char **, u_int *, const u_char *, u_int);
44void mm_inform_authserv(char *, char *); 44void mm_inform_authserv(char *, char *);
45struct passwd *mm_getpwnamallow(const char *); 45struct passwd *mm_getpwnamallow(const char *);
46char *mm_auth2_read_banner(void); 46char *mm_auth2_read_banner(void);
diff --git a/readconf.c b/readconf.c
index a5bb4a25e..42a2961fa 100644
--- a/readconf.c
+++ b/readconf.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: readconf.c,v 1.231 2015/02/02 07:41:40 djm Exp $ */ 1/* $OpenBSD: readconf.c,v 1.232 2015/02/16 22:13:32 djm Exp $ */
2/* 2/*
3 * Author: Tatu Ylonen <ylo@cs.hut.fi> 3 * Author: Tatu Ylonen <ylo@cs.hut.fi>
4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -1480,7 +1480,8 @@ parse_int:
1480 1480
1481 case oUpdateHostkeys: 1481 case oUpdateHostkeys:
1482 intptr = &options->update_hostkeys; 1482 intptr = &options->update_hostkeys;
1483 goto parse_flag; 1483 multistate_ptr = multistate_yesnoask;
1484 goto parse_multistate;
1484 1485
1485 case oHostbasedKeyTypes: 1486 case oHostbasedKeyTypes:
1486 charptr = &options->hostbased_key_types; 1487 charptr = &options->hostbased_key_types;
@@ -2107,6 +2108,7 @@ fmt_intarg(OpCodes code, int val)
2107 return fmt_multistate_int(val, multistate_addressfamily); 2108 return fmt_multistate_int(val, multistate_addressfamily);
2108 case oVerifyHostKeyDNS: 2109 case oVerifyHostKeyDNS:
2109 case oStrictHostKeyChecking: 2110 case oStrictHostKeyChecking:
2111 case oUpdateHostkeys:
2110 return fmt_multistate_int(val, multistate_yesnoask); 2112 return fmt_multistate_int(val, multistate_yesnoask);
2111 case oControlMaster: 2113 case oControlMaster:
2112 return fmt_multistate_int(val, multistate_controlmaster); 2114 return fmt_multistate_int(val, multistate_controlmaster);
diff --git a/readconf.h b/readconf.h
index 701b9c696..576b9e352 100644
--- a/readconf.h
+++ b/readconf.h
@@ -1,4 +1,4 @@
1/* $OpenBSD: readconf.h,v 1.108 2015/01/30 11:43:14 djm Exp $ */ 1/* $OpenBSD: readconf.h,v 1.109 2015/02/16 22:13:32 djm Exp $ */
2 2
3/* 3/*
4 * Author: Tatu Ylonen <ylo@cs.hut.fi> 4 * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -148,7 +148,7 @@ typedef struct {
148 148
149 int fingerprint_hash; 149 int fingerprint_hash;
150 150
151 int update_hostkeys; 151 int update_hostkeys; /* one of SSH_UPDATE_HOSTKEYS_* */
152 152
153 char *hostbased_key_types; 153 char *hostbased_key_types;
154 154
@@ -174,6 +174,10 @@ typedef struct {
174#define SSHCONF_USERCONF 2 /* user provided config file not system */ 174#define SSHCONF_USERCONF 2 /* user provided config file not system */
175#define SSHCONF_POSTCANON 4 /* After hostname canonicalisation */ 175#define SSHCONF_POSTCANON 4 /* After hostname canonicalisation */
176 176
177#define SSH_UPDATE_HOSTKEYS_NO 0
178#define SSH_UPDATE_HOSTKEYS_YES 1
179#define SSH_UPDATE_HOSTKEYS_ASK 2
180
177void initialize_options(Options *); 181void initialize_options(Options *);
178void fill_default_options(Options *); 182void fill_default_options(Options *);
179void fill_default_options_for_canonicalization(Options *); 183void fill_default_options_for_canonicalization(Options *);
diff --git a/serverloop.c b/serverloop.c
index 48bb3f631..5633ceb41 100644
--- a/serverloop.c
+++ b/serverloop.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: serverloop.c,v 1.176 2015/01/20 23:14:00 deraadt Exp $ */ 1/* $OpenBSD: serverloop.c,v 1.177 2015/02/16 22:13:32 djm Exp $ */
2/* 2/*
3 * Author: Tatu Ylonen <ylo@cs.hut.fi> 3 * Author: Tatu Ylonen <ylo@cs.hut.fi>
4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -79,6 +79,7 @@
79#include "auth-options.h" 79#include "auth-options.h"
80#include "serverloop.h" 80#include "serverloop.h"
81#include "roaming.h" 81#include "roaming.h"
82#include "ssherr.h"
82 83
83extern ServerOptions options; 84extern ServerOptions options;
84 85
@@ -1150,11 +1151,82 @@ server_input_channel_open(int type, u_int32_t seq, void *ctxt)
1150} 1151}
1151 1152
1152static int 1153static int
1154server_input_hostkeys_prove(struct sshbuf **respp)
1155{
1156 struct ssh *ssh = active_state; /* XXX */
1157 struct sshbuf *resp = NULL;
1158 struct sshbuf *sigbuf = NULL;
1159 struct sshkey *key = NULL, *key_pub = NULL, *key_prv = NULL;
1160 int r, ndx, success = 0;
1161 const u_char *blob;
1162 u_char *sig = 0;
1163 size_t blen, slen;
1164
1165 if ((resp = sshbuf_new()) == NULL || (sigbuf = sshbuf_new()) == NULL)
1166 fatal("%s: sshbuf_new", __func__);
1167
1168 while (ssh_packet_remaining(ssh) > 0) {
1169 sshkey_free(key);
1170 key = NULL;
1171 if ((r = sshpkt_get_string_direct(ssh, &blob, &blen)) != 0 ||
1172 (r = sshkey_from_blob(blob, blen, &key)) != 0) {
1173 error("%s: couldn't parse key: %s",
1174 __func__, ssh_err(r));
1175 goto out;
1176 }
1177 /*
1178 * Better check that this is actually one of our hostkeys
1179 * before attempting to sign anything with it.
1180 */
1181 if ((ndx = ssh->kex->host_key_index(key, 1, ssh)) == -1) {
1182 error("%s: unknown host %s key",
1183 __func__, sshkey_type(key));
1184 goto out;
1185 }
1186 /*
1187 * XXX refactor: make kex->sign just use an index rather
1188 * than passing in public and private keys
1189 */
1190 if ((key_prv = get_hostkey_by_index(ndx)) == NULL &&
1191 (key_pub = get_hostkey_public_by_index(ndx, ssh)) == NULL) {
1192 error("%s: can't retrieve hostkey %d", __func__, ndx);
1193 goto out;
1194 }
1195 sshbuf_reset(sigbuf);
1196 free(sig);
1197 sig = NULL;
1198 if ((r = sshbuf_put_string(sigbuf,
1199 ssh->kex->session_id, ssh->kex->session_id_len)) != 0 ||
1200 (r = sshbuf_put_cstring(sigbuf,
1201 "hostkeys-prove@openssh.com")) != 0 ||
1202 (r = sshkey_puts(key, sigbuf)) != 0 ||
1203 (r = ssh->kex->sign(key_prv, key_pub, &sig, &slen,
1204 sshbuf_ptr(sigbuf), sshbuf_len(sigbuf), 0)) != 0 ||
1205 (r = sshbuf_put_string(resp, sig, slen)) != 0) {
1206 error("%s: couldn't prepare signature: %s",
1207 __func__, ssh_err(r));
1208 goto out;
1209 }
1210 }
1211 /* Success */
1212 *respp = resp;
1213 resp = NULL; /* don't free it */
1214 success = 1;
1215 out:
1216 free(sig);
1217 sshbuf_free(resp);
1218 sshbuf_free(sigbuf);
1219 sshkey_free(key);
1220 return success;
1221}
1222
1223static int
1153server_input_global_request(int type, u_int32_t seq, void *ctxt) 1224server_input_global_request(int type, u_int32_t seq, void *ctxt)
1154{ 1225{
1155 char *rtype; 1226 char *rtype;
1156 int want_reply; 1227 int want_reply;
1157 int success = 0, allocated_listen_port = 0; 1228 int r, success = 0, allocated_listen_port = 0;
1229 struct sshbuf *resp = NULL;
1158 1230
1159 rtype = packet_get_string(NULL); 1231 rtype = packet_get_string(NULL);
1160 want_reply = packet_get_char(); 1232 want_reply = packet_get_char();
@@ -1191,6 +1263,10 @@ server_input_global_request(int type, u_int32_t seq, void *ctxt)
1191 &allocated_listen_port, &options.fwd_opts); 1263 &allocated_listen_port, &options.fwd_opts);
1192 } 1264 }
1193 free(fwd.listen_host); 1265 free(fwd.listen_host);
1266 if ((resp = sshbuf_new()) == NULL)
1267 fatal("%s: sshbuf_new", __func__);
1268 if ((r = sshbuf_put_u32(resp, allocated_listen_port)) != 0)
1269 fatal("%s: sshbuf_put_u32: %s", __func__, ssh_err(r));
1194 } else if (strcmp(rtype, "cancel-tcpip-forward") == 0) { 1270 } else if (strcmp(rtype, "cancel-tcpip-forward") == 0) {
1195 struct Forward fwd; 1271 struct Forward fwd;
1196 1272
@@ -1234,16 +1310,20 @@ server_input_global_request(int type, u_int32_t seq, void *ctxt)
1234 } else if (strcmp(rtype, "no-more-sessions@openssh.com") == 0) { 1310 } else if (strcmp(rtype, "no-more-sessions@openssh.com") == 0) {
1235 no_more_sessions = 1; 1311 no_more_sessions = 1;
1236 success = 1; 1312 success = 1;
1313 } else if (strcmp(rtype, "hostkeys-prove@openssh.com") == 0) {
1314 success = server_input_hostkeys_prove(&resp);
1237 } 1315 }
1238 if (want_reply) { 1316 if (want_reply) {
1239 packet_start(success ? 1317 packet_start(success ?
1240 SSH2_MSG_REQUEST_SUCCESS : SSH2_MSG_REQUEST_FAILURE); 1318 SSH2_MSG_REQUEST_SUCCESS : SSH2_MSG_REQUEST_FAILURE);
1241 if (success && allocated_listen_port > 0) 1319 if (success && resp != NULL)
1242 packet_put_int(allocated_listen_port); 1320 ssh_packet_put_raw(active_state, sshbuf_ptr(resp),
1321 sshbuf_len(resp));
1243 packet_send(); 1322 packet_send();
1244 packet_write_wait(); 1323 packet_write_wait();
1245 } 1324 }
1246 free(rtype); 1325 free(rtype);
1326 sshbuf_free(resp);
1247 return 0; 1327 return 0;
1248} 1328}
1249 1329
diff --git a/ssh_api.c b/ssh_api.c
index 7097c063c..265a3e639 100644
--- a/ssh_api.c
+++ b/ssh_api.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: ssh_api.c,v 1.3 2015/01/30 01:13:33 djm Exp $ */ 1/* $OpenBSD: ssh_api.c,v 1.4 2015/02/16 22:13:32 djm Exp $ */
2/* 2/*
3 * Copyright (c) 2012 Markus Friedl. All rights reserved. 3 * Copyright (c) 2012 Markus Friedl. All rights reserved.
4 * 4 *
@@ -41,7 +41,7 @@ int _ssh_verify_host_key(struct sshkey *, struct ssh *);
41struct sshkey *_ssh_host_public_key(int, int, struct ssh *); 41struct sshkey *_ssh_host_public_key(int, int, struct ssh *);
42struct sshkey *_ssh_host_private_key(int, int, struct ssh *); 42struct sshkey *_ssh_host_private_key(int, int, struct ssh *);
43int _ssh_host_key_sign(struct sshkey *, struct sshkey *, u_char **, 43int _ssh_host_key_sign(struct sshkey *, struct sshkey *, u_char **,
44 size_t *, u_char *, size_t, u_int); 44 size_t *, const u_char *, size_t, u_int);
45 45
46/* 46/*
47 * stubs for the server side implementation of kex. 47 * stubs for the server side implementation of kex.
@@ -524,7 +524,8 @@ _ssh_order_hostkeyalgs(struct ssh *ssh)
524 524
525int 525int
526_ssh_host_key_sign(struct sshkey *privkey, struct sshkey *pubkey, 526_ssh_host_key_sign(struct sshkey *privkey, struct sshkey *pubkey,
527 u_char **signature, size_t *slen, u_char *data, size_t dlen, u_int compat) 527 u_char **signature, size_t *slen,
528 const u_char *data, size_t dlen, u_int compat)
528{ 529{
529 return sshkey_sign(privkey, signature, slen, data, dlen, compat); 530 return sshkey_sign(privkey, signature, slen, data, dlen, compat);
530} 531}
diff --git a/ssh_config.5 b/ssh_config.5
index ce79fe03f..fa59c518e 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -33,8 +33,8 @@
33.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 33.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
34.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35.\" 35.\"
36.\" $OpenBSD: ssh_config.5,v 1.203 2015/02/02 07:41:40 djm Exp $ 36.\" $OpenBSD: ssh_config.5,v 1.204 2015/02/16 22:13:32 djm Exp $
37.Dd $Mdocdate: February 2 2015 $ 37.Dd $Mdocdate: February 16 2015 $
38.Dt SSH_CONFIG 5 38.Dt SSH_CONFIG 5
39.Os 39.Os
40.Sh NAME 40.Sh NAME
@@ -1510,15 +1510,20 @@ should accept notifications of additional hostkeys from the server sent
1510after authentication has completed and add them to 1510after authentication has completed and add them to
1511.Cm UserKnownHostsFile . 1511.Cm UserKnownHostsFile .
1512The argument must be 1512The argument must be
1513.Dq yes 1513.Dq yes ,
1514or
1515.Dq no 1514.Dq no
1516(the default). 1515(the default) or
1516.Dq ask .
1517Enabling this option allows learning alternate hostkeys for a server 1517Enabling this option allows learning alternate hostkeys for a server
1518and supports graceful key rotation by allowing a server to send replacement 1518and supports graceful key rotation by allowing a server to send replacement
1519public keys before old ones are removed. 1519public keys before old ones are removed.
1520Additional hostkeys are only accepted if the key used to authenticate the 1520Additional hostkeys are only accepted if the key used to authenticate the
1521host was already trusted or explicity accepted by the user. 1521host was already trusted or explicity accepted by the user.
1522If
1523.Cm UpdateHostKeys
1524is set to
1525.Dq ask ,
1526then the user is asked to confirm the modifications to the known_hosts file.
1522.Pp 1527.Pp
1523Presently, only 1528Presently, only
1524.Xr sshd 8 1529.Xr sshd 8
diff --git a/sshd.c b/sshd.c
index 4282bdc1b..aaa63d497 100644
--- a/sshd.c
+++ b/sshd.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: sshd.c,v 1.441 2015/01/31 20:30:05 djm Exp $ */ 1/* $OpenBSD: sshd.c,v 1.442 2015/02/16 22:13:32 djm Exp $ */
2/* 2/*
3 * Author: Tatu Ylonen <ylo@cs.hut.fi> 3 * Author: Tatu Ylonen <ylo@cs.hut.fi>
4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -894,18 +894,25 @@ get_hostkey_public_by_index(int ind, struct ssh *ssh)
894} 894}
895 895
896int 896int
897get_hostkey_index(Key *key, struct ssh *ssh) 897get_hostkey_index(Key *key, int compare, struct ssh *ssh)
898{ 898{
899 int i; 899 int i;
900 900
901 for (i = 0; i < options.num_host_key_files; i++) { 901 for (i = 0; i < options.num_host_key_files; i++) {
902 if (key_is_cert(key)) { 902 if (key_is_cert(key)) {
903 if (key == sensitive_data.host_certificates[i]) 903 if (key == sensitive_data.host_certificates[i] ||
904 (compare && sensitive_data.host_certificates[i] &&
905 sshkey_equal(key,
906 sensitive_data.host_certificates[i])))
904 return (i); 907 return (i);
905 } else { 908 } else {
906 if (key == sensitive_data.host_keys[i]) 909 if (key == sensitive_data.host_keys[i] ||
910 (compare && sensitive_data.host_keys[i] &&
911 sshkey_equal(key, sensitive_data.host_keys[i])))
907 return (i); 912 return (i);
908 if (key == sensitive_data.host_pubkeys[i]) 913 if (key == sensitive_data.host_pubkeys[i] ||
914 (compare && sensitive_data.host_pubkeys[i] &&
915 sshkey_equal(key, sensitive_data.host_pubkeys[i])))
909 return (i); 916 return (i);
910 } 917 }
911 } 918 }
@@ -933,19 +940,23 @@ notify_hostkeys(struct ssh *ssh)
933 debug3("%s: key %d: %s %s", __func__, i, 940 debug3("%s: key %d: %s %s", __func__, i,
934 sshkey_ssh_name(key), fp); 941 sshkey_ssh_name(key), fp);
935 free(fp); 942 free(fp);
936 if ((r = sshkey_puts(key, buf)) != 0) 943 if (nkeys == 0) {
944 packet_start(SSH2_MSG_GLOBAL_REQUEST);
945 packet_put_cstring("hostkeys@openssh.com");
946 packet_put_char(0); /* want-reply */
947 }
948 sshbuf_reset(buf);
949 if ((r = sshkey_putb(key, buf)) != 0)
937 fatal("%s: couldn't put hostkey %d: %s", 950 fatal("%s: couldn't put hostkey %d: %s",
938 __func__, i, ssh_err(r)); 951 __func__, i, ssh_err(r));
952 packet_put_string(sshbuf_ptr(buf), sshbuf_len(buf));
939 nkeys++; 953 nkeys++;
940 } 954 }
955 debug3("%s: sent %d hostkeys", __func__, nkeys);
941 if (nkeys == 0) 956 if (nkeys == 0)
942 fatal("%s: no hostkeys", __func__); 957 fatal("%s: no hostkeys", __func__);
943 debug3("%s: send %d hostkeys", __func__, nkeys);
944 packet_start(SSH2_MSG_GLOBAL_REQUEST);
945 packet_put_cstring("hostkeys@openssh.com");
946 packet_put_char(0); /* want-reply */
947 packet_put_string(sshbuf_ptr(buf), sshbuf_len(buf));
948 packet_send(); 958 packet_send();
959 sshbuf_free(buf);
949} 960}
950 961
951/* 962/*
@@ -2484,7 +2495,7 @@ do_ssh1_kex(void)
2484 2495
2485int 2496int
2486sshd_hostkey_sign(Key *privkey, Key *pubkey, u_char **signature, size_t *slen, 2497sshd_hostkey_sign(Key *privkey, Key *pubkey, u_char **signature, size_t *slen,
2487 u_char *data, size_t dlen, u_int flag) 2498 const u_char *data, size_t dlen, u_int flag)
2488{ 2499{
2489 int r; 2500 int r;
2490 u_int xxx_slen, xxx_dlen = dlen; 2501 u_int xxx_slen, xxx_dlen = dlen;
diff --git a/ssherr.c b/ssherr.c
index 5c29c467c..4ca793992 100644
--- a/ssherr.c
+++ b/ssherr.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: ssherr.c,v 1.3 2015/01/30 01:13:33 djm Exp $ */ 1/* $OpenBSD: ssherr.c,v 1.4 2015/02/16 22:13:32 djm Exp $ */
2/* 2/*
3 * Copyright (c) 2011 Damien Miller 3 * Copyright (c) 2011 Damien Miller
4 * 4 *
@@ -121,6 +121,8 @@ ssh_err(int n)
121 return "agent not present"; 121 return "agent not present";
122 case SSH_ERR_AGENT_NO_IDENTITIES: 122 case SSH_ERR_AGENT_NO_IDENTITIES:
123 return "agent contains no identities"; 123 return "agent contains no identities";
124 case SSH_ERR_BUFFER_READ_ONLY:
125 return "internal error: buffer is read-only";
124 case SSH_ERR_KRL_BAD_MAGIC: 126 case SSH_ERR_KRL_BAD_MAGIC:
125 return "KRL file has invalid magic number"; 127 return "KRL file has invalid magic number";
126 case SSH_ERR_KEY_REVOKED: 128 case SSH_ERR_KEY_REVOKED: