summaryrefslogtreecommitdiff
path: root/clientloop.c
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 /clientloop.c
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@
Diffstat (limited to 'clientloop.c')
-rw-r--r--clientloop.c353
1 files changed, 312 insertions, 41 deletions
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