diff options
author | djm@openbsd.org <djm@openbsd.org> | 2015-02-16 22:13:32 +0000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2015-02-17 09:32:32 +1100 |
commit | 523463a3a2a9bfc6cfc5afa01bae9147f76a37cc (patch) | |
tree | 772be92cee9553c19d51b4570113c3d4de0c2d8b | |
parent | 6c5c949782d86a6e7d58006599c7685bfcd01685 (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-- | PROTOCOL | 53 | ||||
-rw-r--r-- | auth.h | 7 | ||||
-rw-r--r-- | clientloop.c | 353 | ||||
-rw-r--r-- | kex.h | 6 | ||||
-rw-r--r-- | monitor.c | 45 | ||||
-rw-r--r-- | monitor_wrap.c | 7 | ||||
-rw-r--r-- | monitor_wrap.h | 4 | ||||
-rw-r--r-- | readconf.c | 6 | ||||
-rw-r--r-- | readconf.h | 8 | ||||
-rw-r--r-- | serverloop.c | 88 | ||||
-rw-r--r-- | ssh_api.c | 7 | ||||
-rw-r--r-- | ssh_config.5 | 15 | ||||
-rw-r--r-- | sshd.c | 35 | ||||
-rw-r--r-- | ssherr.c | 4 |
14 files changed, 537 insertions, 101 deletions
@@ -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 | ||
42 | OpenSSH introduces new public key algorithms to support certificate | 42 | OpenSSH introduces new public key algorithms to support certificate |
43 | authentication for users and hostkeys. These methods are documented in | 43 | authentication for users and host keys. These methods are documented |
44 | the file PROTOCOL.certkeys | 44 | in the file PROTOCOL.certkeys |
45 | 45 | ||
46 | 1.4. transport: Elliptic Curve cryptography | 46 | 1.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 | ||
285 | 2.5. connection: hostkey update and rotation "hostkeys@openssh.com" | 285 | 2.5. connection: hostkey update and rotation "hostkeys@openssh.com" |
286 | and "hostkeys-prove@openssh.com" | ||
286 | 287 | ||
287 | OpenSSH supports a protocol extension allowing a server to inform | 288 | OpenSSH supports a protocol extension allowing a server to inform |
288 | a client of all its protocol v.2 hostkeys after user-authentication | 289 | a client of all its protocol v.2 host keys after user-authentication |
289 | has completed. | 290 | has 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 | ||
295 | Upon receiving this message, a client may update its known_hosts | 296 | Upon receiving this message, a client should check which of the |
296 | file, adding keys that it has not seen before and deleting keys | 297 | supplied host keys are present in known_hosts. For keys that are |
297 | for the server host that are no longer offered. | 298 | not present, it should send a "hostkeys-prove@openssh.com" message |
299 | to request the server prove ownership of the private half of the | ||
300 | key. | ||
298 | 301 | ||
299 | This extension allows a client to learn key types that it had | 302 | byte SSH_MSG_GLOBAL_REQUEST |
300 | not previously encountered, thereby allowing it to potentially | 303 | string "hostkeys-prove@openssh.com" |
301 | upgrade from weaker key algorithms to better ones. It also | 304 | char 1 /* want-reply */ |
302 | supports graceful key rotation: a server may offer multiple keys | 305 | string[] hostkeys |
303 | of the same type for a period (to give clients an opportunity to | 306 | |
304 | learn them using this extension) before removing the deprecated | 307 | When a server receives this message, it should generate a signature |
305 | key from those offered. | 308 | using each requested key over the following: |
309 | |||
310 | string session identifier | ||
311 | string "hostkeys-prove@openssh.com" | ||
312 | string hostkey | ||
313 | |||
314 | These signatures should be included in the reply, in the order matching | ||
315 | the hostkeys in the request: | ||
316 | |||
317 | byte SSH_MSG_REQUEST_SUCCESS | ||
318 | string[] signatures | ||
319 | |||
320 | When the client receives this reply (and not a failure), it should | ||
321 | validate the signatures and may update its known_hosts file, adding keys | ||
322 | that it has not seen before and deleting keys for the server host that | ||
323 | are no longer offered. | ||
324 | |||
325 | These extensions let a client learn key types that it had not previously | ||
326 | encountered, thereby allowing it to potentially upgrade from weaker | ||
327 | key algorithms to better ones. It also supports graceful key rotation: | ||
328 | a server may offer multiple keys of the same type for a period (to | ||
329 | give clients an opportunity to learn them using this extension) before | ||
330 | removing the deprecated key from those offered. | ||
306 | 331 | ||
307 | 3. SFTP protocol changes | 332 | 3. SFTP protocol changes |
308 | 333 | ||
@@ -428,4 +453,4 @@ respond with a SSH_FXP_STATUS message. | |||
428 | This extension is advertised in the SSH_FXP_VERSION hello with version | 453 | This 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 $ |
@@ -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); | |||
206 | Key *get_hostkey_public_by_index(int, struct ssh *); | 206 | Key *get_hostkey_public_by_index(int, struct ssh *); |
207 | Key *get_hostkey_public_by_type(int, int, struct ssh *); | 207 | Key *get_hostkey_public_by_type(int, int, struct ssh *); |
208 | Key *get_hostkey_private_by_type(int, int, struct ssh *); | 208 | Key *get_hostkey_private_by_type(int, int, struct ssh *); |
209 | int get_hostkey_index(Key *, struct ssh *); | 209 | int get_hostkey_index(Key *, int, struct ssh *); |
210 | int ssh1_session_key(BIGNUM *); | 210 | int ssh1_session_key(BIGNUM *); |
211 | int sshd_hostkey_sign(Key *, Key *, u_char **, size_t *, u_char *, size_t, u_int); | 211 | int 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 */ |
214 | void auth_debug_add(const char *fmt,...) __attribute__((format(printf, 1, 2))); | 215 | void 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 | ||
2092 | struct 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 | |||
2117 | static void | ||
2118 | hostkeys_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 | |||
2136 | static int | ||
2137 | hostkeys_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 | |||
2170 | static void | ||
2171 | update_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 | |||
2234 | static void | ||
2235 | client_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) | |||
2097 | static int | 2307 | static int |
2098 | client_input_hostkeys(void) | 2308 | client_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 | ||
2186 | static int | 2457 | static int |
@@ -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 */ |
@@ -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) | |||
685 | int | 685 | int |
686 | mm_answer_sign(int sock, Buffer *m) | 686 | mm_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 | ||
221 | int | 221 | int |
222 | mm_key_sign(Key *key, u_char **sigp, u_int *lenp, u_char *data, u_int datalen) | 222 | mm_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; | |||
40 | void mm_log_handler(LogLevel, const char *, void *); | 40 | void mm_log_handler(LogLevel, const char *, void *); |
41 | int mm_is_monitor(void); | 41 | int mm_is_monitor(void); |
42 | DH *mm_choose_dh(int, int, int); | 42 | DH *mm_choose_dh(int, int, int); |
43 | int mm_key_sign(Key *, u_char **, u_int *, u_char *, u_int); | 43 | int mm_key_sign(Key *, u_char **, u_int *, const u_char *, u_int); |
44 | void mm_inform_authserv(char *, char *); | 44 | void mm_inform_authserv(char *, char *); |
45 | struct passwd *mm_getpwnamallow(const char *); | 45 | struct passwd *mm_getpwnamallow(const char *); |
46 | char *mm_auth2_read_banner(void); | 46 | char *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 | |||
177 | void initialize_options(Options *); | 181 | void initialize_options(Options *); |
178 | void fill_default_options(Options *); | 182 | void fill_default_options(Options *); |
179 | void fill_default_options_for_canonicalization(Options *); | 183 | void 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 | ||
83 | extern ServerOptions options; | 84 | extern ServerOptions options; |
84 | 85 | ||
@@ -1150,11 +1151,82 @@ server_input_channel_open(int type, u_int32_t seq, void *ctxt) | |||
1150 | } | 1151 | } |
1151 | 1152 | ||
1152 | static int | 1153 | static int |
1154 | server_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 | |||
1223 | static int | ||
1153 | server_input_global_request(int type, u_int32_t seq, void *ctxt) | 1224 | server_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 | ||
@@ -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 *); | |||
41 | struct sshkey *_ssh_host_public_key(int, int, struct ssh *); | 41 | struct sshkey *_ssh_host_public_key(int, int, struct ssh *); |
42 | struct sshkey *_ssh_host_private_key(int, int, struct ssh *); | 42 | struct sshkey *_ssh_host_private_key(int, int, struct ssh *); |
43 | int _ssh_host_key_sign(struct sshkey *, struct sshkey *, u_char **, | 43 | int _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 | ||
525 | int | 525 | int |
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 | |||
1510 | after authentication has completed and add them to | 1510 | after authentication has completed and add them to |
1511 | .Cm UserKnownHostsFile . | 1511 | .Cm UserKnownHostsFile . |
1512 | The argument must be | 1512 | The argument must be |
1513 | .Dq yes | 1513 | .Dq yes , |
1514 | or | ||
1515 | .Dq no | 1514 | .Dq no |
1516 | (the default). | 1515 | (the default) or |
1516 | .Dq ask . | ||
1517 | Enabling this option allows learning alternate hostkeys for a server | 1517 | Enabling this option allows learning alternate hostkeys for a server |
1518 | and supports graceful key rotation by allowing a server to send replacement | 1518 | and supports graceful key rotation by allowing a server to send replacement |
1519 | public keys before old ones are removed. | 1519 | public keys before old ones are removed. |
1520 | Additional hostkeys are only accepted if the key used to authenticate the | 1520 | Additional hostkeys are only accepted if the key used to authenticate the |
1521 | host was already trusted or explicity accepted by the user. | 1521 | host was already trusted or explicity accepted by the user. |
1522 | If | ||
1523 | .Cm UpdateHostKeys | ||
1524 | is set to | ||
1525 | .Dq ask , | ||
1526 | then the user is asked to confirm the modifications to the known_hosts file. | ||
1522 | .Pp | 1527 | .Pp |
1523 | Presently, only | 1528 | Presently, only |
1524 | .Xr sshd 8 | 1529 | .Xr sshd 8 |
@@ -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 | ||
896 | int | 896 | int |
897 | get_hostkey_index(Key *key, struct ssh *ssh) | 897 | get_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 | ||
2485 | int | 2496 | int |
2486 | sshd_hostkey_sign(Key *privkey, Key *pubkey, u_char **signature, size_t *slen, | 2497 | sshd_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; |
@@ -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: |