]>
Commit | Line | Data |
---|---|---|
602f4bee | 1 | /* $OpenBSD: ssh_api.c,v 1.28 2024/01/09 21:39:14 djm Exp $ */ |
f582f0e9 | 2 | /* |
3 | * Copyright (c) 2012 Markus Friedl. All rights reserved. | |
4 | * | |
5 | * Permission to use, copy, modify, and distribute this software for any | |
6 | * purpose with or without fee is hereby granted, provided that the above | |
7 | * copyright notice and this permission notice appear in all copies. | |
8 | * | |
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
16 | */ | |
17 | ||
18 | #include "includes.h" | |
19 | ||
be02d7cb | 20 | #include <sys/types.h> |
21 | ||
22 | #include <stdio.h> | |
23 | #include <stdlib.h> | |
24 | ||
f582f0e9 | 25 | #include "ssh_api.h" |
26 | #include "compat.h" | |
27 | #include "log.h" | |
28 | #include "authfile.h" | |
29 | #include "sshkey.h" | |
30 | #include "misc.h" | |
f582f0e9 | 31 | #include "ssh2.h" |
32 | #include "version.h" | |
33 | #include "myproposal.h" | |
34 | #include "ssherr.h" | |
35 | #include "sshbuf.h" | |
36 | ||
31b49525 DT |
37 | #include "openbsd-compat/openssl-compat.h" |
38 | ||
f582f0e9 | 39 | #include <string.h> |
40 | ||
41 | int _ssh_exchange_banner(struct ssh *); | |
0a843d9a | 42 | int _ssh_send_banner(struct ssh *, struct sshbuf *); |
43 | int _ssh_read_banner(struct ssh *, struct sshbuf *); | |
f582f0e9 | 44 | int _ssh_order_hostkeyalgs(struct ssh *); |
45 | int _ssh_verify_host_key(struct sshkey *, struct ssh *); | |
5104db7c | 46 | struct sshkey *_ssh_host_public_key(int, int, struct ssh *); |
47 | struct sshkey *_ssh_host_private_key(int, int, struct ssh *); | |
04c091fc | 48 | int _ssh_host_key_sign(struct ssh *, struct sshkey *, struct sshkey *, |
49 | u_char **, size_t *, const u_char *, size_t, const char *); | |
f582f0e9 | 50 | |
51 | /* | |
52 | * stubs for the server side implementation of kex. | |
53 | * disable privsep so our stubs will never be called. | |
54 | */ | |
55 | int use_privsep = 0; | |
56 | int mm_sshkey_sign(struct sshkey *, u_char **, u_int *, | |
9b8ad938 | 57 | const u_char *, u_int, const char *, const char *, const char *, u_int); |
670104b9 | 58 | |
59 | #ifdef WITH_OPENSSL | |
f582f0e9 | 60 | DH *mm_choose_dh(int, int, int); |
670104b9 | 61 | #endif |
f582f0e9 | 62 | |
f582f0e9 | 63 | int |
64 | mm_sshkey_sign(struct sshkey *key, u_char **sigp, u_int *lenp, | |
9b8ad938 | 65 | const u_char *data, u_int datalen, const char *alg, |
66 | const char *sk_provider, const char *sk_pin, u_int compat) | |
f582f0e9 | 67 | { |
68 | return (-1); | |
69 | } | |
70 | ||
670104b9 | 71 | #ifdef WITH_OPENSSL |
f582f0e9 | 72 | DH * |
73 | mm_choose_dh(int min, int nbits, int max) | |
74 | { | |
75 | return (NULL); | |
76 | } | |
670104b9 | 77 | #endif |
f582f0e9 | 78 | |
79 | /* API */ | |
80 | ||
81 | int | |
82 | ssh_init(struct ssh **sshp, int is_server, struct kex_params *kex_params) | |
83 | { | |
31d8d231 | 84 | char *myproposal[PROPOSAL_MAX] = { KEX_CLIENT }; |
602f4bee | 85 | char *populated[PROPOSAL_MAX]; |
f582f0e9 | 86 | struct ssh *ssh; |
87 | char **proposal; | |
88 | static int called; | |
89 | int r; | |
90 | ||
91 | if (!called) { | |
42c5ec4b | 92 | seed_rng(); |
f582f0e9 | 93 | called = 1; |
94 | } | |
95 | ||
4509b5d4 | 96 | if ((ssh = ssh_packet_set_connection(NULL, -1, -1)) == NULL) |
97 | return SSH_ERR_ALLOC_FAIL; | |
f582f0e9 | 98 | if (is_server) |
99 | ssh_packet_set_server(ssh); | |
100 | ||
101 | /* Initialize key exchange */ | |
102 | proposal = kex_params ? kex_params->proposal : myproposal; | |
602f4bee | 103 | kex_proposal_populate_entries(ssh, populated, |
104 | proposal[PROPOSAL_KEX_ALGS], | |
105 | proposal[PROPOSAL_ENC_ALGS_CTOS], | |
106 | proposal[PROPOSAL_MAC_ALGS_CTOS], | |
107 | proposal[PROPOSAL_COMP_ALGS_CTOS], | |
108 | proposal[PROPOSAL_SERVER_HOST_KEY_ALGS]); | |
109 | r = kex_ready(ssh, populated); | |
110 | kex_proposal_free_entries(populated); | |
111 | if (r != 0) { | |
f582f0e9 | 112 | ssh_free(ssh); |
113 | return r; | |
114 | } | |
602f4bee | 115 | |
f582f0e9 | 116 | ssh->kex->server = is_server; |
117 | if (is_server) { | |
118 | #ifdef WITH_OPENSSL | |
aaca72d6 | 119 | ssh->kex->kex[KEX_DH_GRP1_SHA1] = kex_gen_server; |
120 | ssh->kex->kex[KEX_DH_GRP14_SHA1] = kex_gen_server; | |
121 | ssh->kex->kex[KEX_DH_GRP14_SHA256] = kex_gen_server; | |
122 | ssh->kex->kex[KEX_DH_GRP16_SHA512] = kex_gen_server; | |
123 | ssh->kex->kex[KEX_DH_GRP18_SHA512] = kex_gen_server; | |
f582f0e9 | 124 | ssh->kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; |
125 | ssh->kex->kex[KEX_DH_GEX_SHA256] = kexgex_server; | |
f2004cd1 | 126 | # ifdef OPENSSL_HAS_ECC |
aaca72d6 | 127 | ssh->kex->kex[KEX_ECDH_SHA2] = kex_gen_server; |
f2004cd1 | 128 | # endif |
f582f0e9 | 129 | #endif /* WITH_OPENSSL */ |
aaca72d6 | 130 | ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_server; |
2c71cec0 | 131 | ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server; |
f582f0e9 | 132 | ssh->kex->load_host_public_key=&_ssh_host_public_key; |
133 | ssh->kex->load_host_private_key=&_ssh_host_private_key; | |
134 | ssh->kex->sign=&_ssh_host_key_sign; | |
135 | } else { | |
136 | #ifdef WITH_OPENSSL | |
aaca72d6 | 137 | ssh->kex->kex[KEX_DH_GRP1_SHA1] = kex_gen_client; |
138 | ssh->kex->kex[KEX_DH_GRP14_SHA1] = kex_gen_client; | |
139 | ssh->kex->kex[KEX_DH_GRP14_SHA256] = kex_gen_client; | |
140 | ssh->kex->kex[KEX_DH_GRP16_SHA512] = kex_gen_client; | |
141 | ssh->kex->kex[KEX_DH_GRP18_SHA512] = kex_gen_client; | |
f582f0e9 | 142 | ssh->kex->kex[KEX_DH_GEX_SHA1] = kexgex_client; |
143 | ssh->kex->kex[KEX_DH_GEX_SHA256] = kexgex_client; | |
f2004cd1 | 144 | # ifdef OPENSSL_HAS_ECC |
aaca72d6 | 145 | ssh->kex->kex[KEX_ECDH_SHA2] = kex_gen_client; |
f2004cd1 | 146 | # endif |
f582f0e9 | 147 | #endif /* WITH_OPENSSL */ |
aaca72d6 | 148 | ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client; |
2c71cec0 | 149 | ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client; |
f582f0e9 | 150 | ssh->kex->verify_host_key =&_ssh_verify_host_key; |
151 | } | |
152 | *sshp = ssh; | |
153 | return 0; | |
154 | } | |
155 | ||
156 | void | |
157 | ssh_free(struct ssh *ssh) | |
158 | { | |
159 | struct key_entry *k; | |
160 | ||
ace12dc6 | 161 | if (ssh == NULL) |
162 | return; | |
163 | ||
f582f0e9 | 164 | /* |
165 | * we've only created the public keys variants in case we | |
166 | * are a acting as a server. | |
167 | */ | |
168 | while ((k = TAILQ_FIRST(&ssh->public_keys)) != NULL) { | |
169 | TAILQ_REMOVE(&ssh->public_keys, k, next); | |
170 | if (ssh->kex && ssh->kex->server) | |
171 | sshkey_free(k->key); | |
172 | free(k); | |
173 | } | |
174 | while ((k = TAILQ_FIRST(&ssh->private_keys)) != NULL) { | |
175 | TAILQ_REMOVE(&ssh->private_keys, k, next); | |
176 | free(k); | |
177 | } | |
55ef3e9c | 178 | ssh_packet_close(ssh); |
f582f0e9 | 179 | free(ssh); |
180 | } | |
181 | ||
182 | void | |
183 | ssh_set_app_data(struct ssh *ssh, void *app_data) | |
184 | { | |
185 | ssh->app_data = app_data; | |
186 | } | |
187 | ||
188 | void * | |
189 | ssh_get_app_data(struct ssh *ssh) | |
190 | { | |
191 | return ssh->app_data; | |
192 | } | |
193 | ||
194 | /* Returns < 0 on error, 0 otherwise */ | |
195 | int | |
196 | ssh_add_hostkey(struct ssh *ssh, struct sshkey *key) | |
197 | { | |
198 | struct sshkey *pubkey = NULL; | |
199 | struct key_entry *k = NULL, *k_prv = NULL; | |
200 | int r; | |
201 | ||
202 | if (ssh->kex->server) { | |
203 | if ((r = sshkey_from_private(key, &pubkey)) != 0) | |
204 | return r; | |
205 | if ((k = malloc(sizeof(*k))) == NULL || | |
206 | (k_prv = malloc(sizeof(*k_prv))) == NULL) { | |
207 | free(k); | |
208 | sshkey_free(pubkey); | |
209 | return SSH_ERR_ALLOC_FAIL; | |
210 | } | |
211 | k_prv->key = key; | |
212 | TAILQ_INSERT_TAIL(&ssh->private_keys, k_prv, next); | |
213 | ||
214 | /* add the public key, too */ | |
215 | k->key = pubkey; | |
216 | TAILQ_INSERT_TAIL(&ssh->public_keys, k, next); | |
217 | r = 0; | |
218 | } else { | |
219 | if ((k = malloc(sizeof(*k))) == NULL) | |
220 | return SSH_ERR_ALLOC_FAIL; | |
221 | k->key = key; | |
222 | TAILQ_INSERT_TAIL(&ssh->public_keys, k, next); | |
223 | r = 0; | |
224 | } | |
225 | ||
226 | return r; | |
227 | } | |
228 | ||
229 | int | |
230 | ssh_set_verify_host_key_callback(struct ssh *ssh, | |
231 | int (*cb)(struct sshkey *, struct ssh *)) | |
232 | { | |
233 | if (cb == NULL || ssh->kex == NULL) | |
234 | return SSH_ERR_INVALID_ARGUMENT; | |
235 | ||
236 | ssh->kex->verify_host_key = cb; | |
237 | ||
238 | return 0; | |
239 | } | |
240 | ||
241 | int | |
242 | ssh_input_append(struct ssh *ssh, const u_char *data, size_t len) | |
243 | { | |
244 | return sshbuf_put(ssh_packet_get_input(ssh), data, len); | |
245 | } | |
246 | ||
247 | int | |
248 | ssh_packet_next(struct ssh *ssh, u_char *typep) | |
249 | { | |
250 | int r; | |
251 | u_int32_t seqnr; | |
252 | u_char type; | |
253 | ||
254 | /* | |
255 | * Try to read a packet. Return SSH_MSG_NONE if no packet or not | |
256 | * enough data. | |
257 | */ | |
258 | *typep = SSH_MSG_NONE; | |
0a843d9a | 259 | if (sshbuf_len(ssh->kex->client_version) == 0 || |
260 | sshbuf_len(ssh->kex->server_version) == 0) | |
f582f0e9 | 261 | return _ssh_exchange_banner(ssh); |
262 | /* | |
263 | * If we enough data and a dispatch function then | |
264 | * call the function and get the next packet. | |
265 | * Otherwise return the packet type to the caller so it | |
266 | * can decide how to go on. | |
267 | * | |
268 | * We will only call the dispatch function for: | |
269 | * 20-29 Algorithm negotiation | |
270 | * 30-49 Key exchange method specific (numbers can be reused for | |
271 | * different authentication methods) | |
272 | */ | |
273 | for (;;) { | |
274 | if ((r = ssh_packet_read_poll2(ssh, &type, &seqnr)) != 0) | |
275 | return r; | |
276 | if (type > 0 && type < DISPATCH_MAX && | |
277 | type >= SSH2_MSG_KEXINIT && type <= SSH2_MSG_TRANSPORT_MAX && | |
278 | ssh->dispatch[type] != NULL) { | |
279 | if ((r = (*ssh->dispatch[type])(type, seqnr, ssh)) != 0) | |
280 | return r; | |
281 | } else { | |
282 | *typep = type; | |
283 | return 0; | |
284 | } | |
285 | } | |
286 | } | |
287 | ||
288 | const u_char * | |
289 | ssh_packet_payload(struct ssh *ssh, size_t *lenp) | |
290 | { | |
291 | return sshpkt_ptr(ssh, lenp); | |
292 | } | |
293 | ||
294 | int | |
295 | ssh_packet_put(struct ssh *ssh, int type, const u_char *data, size_t len) | |
296 | { | |
297 | int r; | |
298 | ||
299 | if ((r = sshpkt_start(ssh, type)) != 0 || | |
300 | (r = sshpkt_put(ssh, data, len)) != 0 || | |
301 | (r = sshpkt_send(ssh)) != 0) | |
302 | return r; | |
303 | return 0; | |
304 | } | |
305 | ||
306 | const u_char * | |
307 | ssh_output_ptr(struct ssh *ssh, size_t *len) | |
308 | { | |
309 | struct sshbuf *output = ssh_packet_get_output(ssh); | |
310 | ||
311 | *len = sshbuf_len(output); | |
312 | return sshbuf_ptr(output); | |
313 | } | |
314 | ||
315 | int | |
316 | ssh_output_consume(struct ssh *ssh, size_t len) | |
317 | { | |
318 | return sshbuf_consume(ssh_packet_get_output(ssh), len); | |
319 | } | |
320 | ||
321 | int | |
322 | ssh_output_space(struct ssh *ssh, size_t len) | |
323 | { | |
324 | return (0 == sshbuf_check_reserve(ssh_packet_get_output(ssh), len)); | |
325 | } | |
326 | ||
327 | int | |
328 | ssh_input_space(struct ssh *ssh, size_t len) | |
329 | { | |
330 | return (0 == sshbuf_check_reserve(ssh_packet_get_input(ssh), len)); | |
331 | } | |
332 | ||
333 | /* Read other side's version identification. */ | |
334 | int | |
0a843d9a | 335 | _ssh_read_banner(struct ssh *ssh, struct sshbuf *banner) |
f582f0e9 | 336 | { |
0a843d9a | 337 | struct sshbuf *input = ssh_packet_get_input(ssh); |
f582f0e9 | 338 | const char *mismatch = "Protocol mismatch.\r\n"; |
0a843d9a | 339 | const u_char *s = sshbuf_ptr(input); |
340 | u_char c; | |
b36ee3fc | 341 | char *cp = NULL, *remote_version = NULL; |
342 | int r = 0, remote_major, remote_minor, expect_nl; | |
0a843d9a | 343 | size_t n, j; |
f582f0e9 | 344 | |
f582f0e9 | 345 | for (j = n = 0;;) { |
0a843d9a | 346 | sshbuf_reset(banner); |
347 | expect_nl = 0; | |
348 | for (;;) { | |
349 | if (j >= sshbuf_len(input)) | |
350 | return 0; /* insufficient data in input buf */ | |
351 | c = s[j++]; | |
352 | if (c == '\r') { | |
353 | expect_nl = 1; | |
354 | continue; | |
f582f0e9 | 355 | } |
0a843d9a | 356 | if (c == '\n') |
f582f0e9 | 357 | break; |
0a843d9a | 358 | if (expect_nl) |
359 | goto bad; | |
360 | if ((r = sshbuf_put_u8(banner, c)) != 0) | |
361 | return r; | |
362 | if (sshbuf_len(banner) > SSH_MAX_BANNER_LEN) | |
363 | goto bad; | |
f582f0e9 | 364 | } |
0a843d9a | 365 | if (sshbuf_len(banner) >= 4 && |
366 | memcmp(sshbuf_ptr(banner), "SSH-", 4) == 0) | |
f582f0e9 | 367 | break; |
816036f1 | 368 | debug_f("%.*s", (int)sshbuf_len(banner), |
b36ee3fc | 369 | sshbuf_ptr(banner)); |
0a843d9a | 370 | /* Accept lines before banner only on client */ |
371 | if (ssh->kex->server || ++n > SSH_MAX_PRE_BANNER_LINES) { | |
372 | bad: | |
f582f0e9 | 373 | if ((r = sshbuf_put(ssh_packet_get_output(ssh), |
31d8d231 | 374 | mismatch, strlen(mismatch))) != 0) |
f582f0e9 | 375 | return r; |
376 | return SSH_ERR_NO_PROTOCOL_VERSION; | |
377 | } | |
378 | } | |
379 | if ((r = sshbuf_consume(input, j)) != 0) | |
380 | return r; | |
381 | ||
0a843d9a | 382 | /* XXX remote version must be the same size as banner for sscanf */ |
b36ee3fc | 383 | if ((cp = sshbuf_dup_string(banner)) == NULL || |
384 | (remote_version = calloc(1, sshbuf_len(banner))) == NULL) { | |
385 | r = SSH_ERR_ALLOC_FAIL; | |
386 | goto out; | |
387 | } | |
0a843d9a | 388 | |
f582f0e9 | 389 | /* |
390 | * Check that the versions match. In future this might accept | |
391 | * several versions and set appropriate flags to handle them. | |
392 | */ | |
0a843d9a | 393 | if (sscanf(cp, "SSH-%d.%d-%[^\n]\n", |
b36ee3fc | 394 | &remote_major, &remote_minor, remote_version) != 3) { |
395 | r = SSH_ERR_INVALID_FORMAT; | |
396 | goto out; | |
397 | } | |
f582f0e9 | 398 | debug("Remote protocol version %d.%d, remote software version %.100s", |
399 | remote_major, remote_minor, remote_version); | |
400 | ||
4ca6a1fa | 401 | compat_banner(ssh, remote_version); |
f582f0e9 | 402 | if (remote_major == 1 && remote_minor == 99) { |
403 | remote_major = 2; | |
404 | remote_minor = 0; | |
405 | } | |
406 | if (remote_major != 2) | |
b36ee3fc | 407 | r = SSH_ERR_PROTOCOL_MISMATCH; |
408 | ||
0a843d9a | 409 | debug("Remote version string %.100s", cp); |
b36ee3fc | 410 | out: |
0a843d9a | 411 | free(cp); |
b36ee3fc | 412 | free(remote_version); |
413 | return r; | |
f582f0e9 | 414 | } |
415 | ||
416 | /* Send our own protocol version identification. */ | |
417 | int | |
0a843d9a | 418 | _ssh_send_banner(struct ssh *ssh, struct sshbuf *banner) |
f582f0e9 | 419 | { |
0a843d9a | 420 | char *cp; |
f582f0e9 | 421 | int r; |
422 | ||
0a843d9a | 423 | if ((r = sshbuf_putf(banner, "SSH-2.0-%.100s\r\n", SSH_VERSION)) != 0) |
424 | return r; | |
425 | if ((r = sshbuf_putb(ssh_packet_get_output(ssh), banner)) != 0) | |
426 | return r; | |
427 | /* Remove trailing \r\n */ | |
428 | if ((r = sshbuf_consume_end(banner, 2)) != 0) | |
f582f0e9 | 429 | return r; |
0a843d9a | 430 | if ((cp = sshbuf_dup_string(banner)) == NULL) |
f582f0e9 | 431 | return SSH_ERR_ALLOC_FAIL; |
0a843d9a | 432 | debug("Local version string %.100s", cp); |
433 | free(cp); | |
f582f0e9 | 434 | return 0; |
435 | } | |
436 | ||
437 | int | |
438 | _ssh_exchange_banner(struct ssh *ssh) | |
439 | { | |
440 | struct kex *kex = ssh->kex; | |
441 | int r; | |
442 | ||
443 | /* | |
444 | * if _ssh_read_banner() cannot parse a full version string | |
445 | * it will return NULL and we end up calling it again. | |
446 | */ | |
447 | ||
448 | r = 0; | |
449 | if (kex->server) { | |
0a843d9a | 450 | if (sshbuf_len(ssh->kex->server_version) == 0) |
451 | r = _ssh_send_banner(ssh, ssh->kex->server_version); | |
f582f0e9 | 452 | if (r == 0 && |
0a843d9a | 453 | sshbuf_len(ssh->kex->server_version) != 0 && |
454 | sshbuf_len(ssh->kex->client_version) == 0) | |
455 | r = _ssh_read_banner(ssh, ssh->kex->client_version); | |
f582f0e9 | 456 | } else { |
0a843d9a | 457 | if (sshbuf_len(ssh->kex->server_version) == 0) |
458 | r = _ssh_read_banner(ssh, ssh->kex->server_version); | |
f582f0e9 | 459 | if (r == 0 && |
0a843d9a | 460 | sshbuf_len(ssh->kex->server_version) != 0 && |
461 | sshbuf_len(ssh->kex->client_version) == 0) | |
462 | r = _ssh_send_banner(ssh, ssh->kex->client_version); | |
f582f0e9 | 463 | } |
464 | if (r != 0) | |
465 | return r; | |
466 | /* start initial kex as soon as we have exchanged the banners */ | |
0a843d9a | 467 | if (sshbuf_len(ssh->kex->server_version) != 0 && |
468 | sshbuf_len(ssh->kex->client_version) != 0) { | |
f582f0e9 | 469 | if ((r = _ssh_order_hostkeyalgs(ssh)) != 0 || |
470 | (r = kex_send_kexinit(ssh)) != 0) | |
471 | return r; | |
472 | } | |
473 | return 0; | |
474 | } | |
475 | ||
476 | struct sshkey * | |
5104db7c | 477 | _ssh_host_public_key(int type, int nid, struct ssh *ssh) |
f582f0e9 | 478 | { |
479 | struct key_entry *k; | |
480 | ||
816036f1 | 481 | debug3_f("need %d", type); |
f582f0e9 | 482 | TAILQ_FOREACH(k, &ssh->public_keys, next) { |
816036f1 | 483 | debug3_f("check %s", sshkey_type(k->key)); |
5104db7c | 484 | if (k->key->type == type && |
485 | (type != KEY_ECDSA || k->key->ecdsa_nid == nid)) | |
f582f0e9 | 486 | return (k->key); |
487 | } | |
488 | return (NULL); | |
489 | } | |
490 | ||
491 | struct sshkey * | |
5104db7c | 492 | _ssh_host_private_key(int type, int nid, struct ssh *ssh) |
f582f0e9 | 493 | { |
494 | struct key_entry *k; | |
495 | ||
816036f1 | 496 | debug3_f("need %d", type); |
f582f0e9 | 497 | TAILQ_FOREACH(k, &ssh->private_keys, next) { |
816036f1 | 498 | debug3_f("check %s", sshkey_type(k->key)); |
5104db7c | 499 | if (k->key->type == type && |
500 | (type != KEY_ECDSA || k->key->ecdsa_nid == nid)) | |
f582f0e9 | 501 | return (k->key); |
502 | } | |
503 | return (NULL); | |
504 | } | |
505 | ||
506 | int | |
507 | _ssh_verify_host_key(struct sshkey *hostkey, struct ssh *ssh) | |
508 | { | |
509 | struct key_entry *k; | |
510 | ||
816036f1 | 511 | debug3_f("need %s", sshkey_type(hostkey)); |
f582f0e9 | 512 | TAILQ_FOREACH(k, &ssh->public_keys, next) { |
816036f1 | 513 | debug3_f("check %s", sshkey_type(k->key)); |
f582f0e9 | 514 | if (sshkey_equal_public(hostkey, k->key)) |
515 | return (0); /* ok */ | |
516 | } | |
517 | return (-1); /* failed */ | |
518 | } | |
519 | ||
520 | /* offer hostkey algorithms in kexinit depending on registered keys */ | |
521 | int | |
522 | _ssh_order_hostkeyalgs(struct ssh *ssh) | |
523 | { | |
524 | struct key_entry *k; | |
525 | char *orig, *avail, *oavail = NULL, *alg, *replace = NULL; | |
526 | char **proposal; | |
527 | size_t maxlen; | |
528 | int ktype, r; | |
529 | ||
530 | /* XXX we de-serialize ssh->kex->my, modify it, and change it */ | |
531 | if ((r = kex_buf2prop(ssh->kex->my, NULL, &proposal)) != 0) | |
532 | return r; | |
533 | orig = proposal[PROPOSAL_SERVER_HOST_KEY_ALGS]; | |
534 | if ((oavail = avail = strdup(orig)) == NULL) { | |
535 | r = SSH_ERR_ALLOC_FAIL; | |
536 | goto out; | |
537 | } | |
538 | maxlen = strlen(avail) + 1; | |
539 | if ((replace = calloc(1, maxlen)) == NULL) { | |
540 | r = SSH_ERR_ALLOC_FAIL; | |
541 | goto out; | |
542 | } | |
543 | *replace = '\0'; | |
544 | while ((alg = strsep(&avail, ",")) && *alg != '\0') { | |
545 | if ((ktype = sshkey_type_from_name(alg)) == KEY_UNSPEC) | |
546 | continue; | |
547 | TAILQ_FOREACH(k, &ssh->public_keys, next) { | |
548 | if (k->key->type == ktype || | |
549 | (sshkey_is_cert(k->key) && k->key->type == | |
550 | sshkey_type_plain(ktype))) { | |
551 | if (*replace != '\0') | |
552 | strlcat(replace, ",", maxlen); | |
553 | strlcat(replace, alg, maxlen); | |
554 | break; | |
555 | } | |
556 | } | |
557 | } | |
558 | if (*replace != '\0') { | |
816036f1 | 559 | debug2_f("orig/%d %s", ssh->kex->server, orig); |
560 | debug2_f("replace/%d %s", ssh->kex->server, replace); | |
f582f0e9 | 561 | free(orig); |
562 | proposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = replace; | |
563 | replace = NULL; /* owned by proposal */ | |
564 | r = kex_prop2buf(ssh->kex->my, proposal); | |
565 | } | |
566 | out: | |
567 | free(oavail); | |
568 | free(replace); | |
569 | kex_prop_free(proposal); | |
570 | return r; | |
571 | } | |
572 | ||
573 | int | |
04c091fc | 574 | _ssh_host_key_sign(struct ssh *ssh, struct sshkey *privkey, |
575 | struct sshkey *pubkey, u_char **signature, size_t *slen, | |
576 | const u_char *data, size_t dlen, const char *alg) | |
f582f0e9 | 577 | { |
04c091fc | 578 | return sshkey_sign(privkey, signature, slen, data, dlen, |
9b8ad938 | 579 | alg, NULL, NULL, ssh->compat); |
f582f0e9 | 580 | } |