diff options
author | markus@openbsd.org <markus@openbsd.org> | 2015-01-19 20:30:23 +0000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2015-01-20 09:23:46 +1100 |
commit | f582f0e917bb0017b00944783cd5f408bf4b0b5e (patch) | |
tree | 4f5642958dd85b4b81937498dc47d79cba91d67b /ssh_api.c | |
parent | 48b3b2ba75181f11fca7f327058a591f4426cade (diff) |
upstream commit
add experimental api for packet layer; ok djm@
Diffstat (limited to 'ssh_api.c')
-rw-r--r-- | ssh_api.c | 527 |
1 files changed, 527 insertions, 0 deletions
diff --git a/ssh_api.c b/ssh_api.c new file mode 100644 index 000000000..1df995c94 --- /dev/null +++ b/ssh_api.c | |||
@@ -0,0 +1,527 @@ | |||
1 | /* $OpenBSD: ssh_api.c,v 1.1 2015/01/19 20:30:23 markus Exp $ */ | ||
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 | |||
20 | #include "ssh1.h" /* For SSH_MSG_NONE */ | ||
21 | #include "ssh_api.h" | ||
22 | #include "compat.h" | ||
23 | #include "log.h" | ||
24 | #include "authfile.h" | ||
25 | #include "sshkey.h" | ||
26 | #include "misc.h" | ||
27 | #include "ssh1.h" | ||
28 | #include "ssh2.h" | ||
29 | #include "version.h" | ||
30 | #include "myproposal.h" | ||
31 | #include "ssherr.h" | ||
32 | #include "sshbuf.h" | ||
33 | |||
34 | #include <string.h> | ||
35 | |||
36 | int _ssh_exchange_banner(struct ssh *); | ||
37 | int _ssh_send_banner(struct ssh *, char **); | ||
38 | int _ssh_read_banner(struct ssh *, char **); | ||
39 | int _ssh_order_hostkeyalgs(struct ssh *); | ||
40 | int _ssh_verify_host_key(struct sshkey *, struct ssh *); | ||
41 | struct sshkey *_ssh_host_public_key(int, struct ssh *); | ||
42 | struct sshkey *_ssh_host_private_key(int, struct ssh *); | ||
43 | int _ssh_host_key_sign(struct sshkey *, struct sshkey *, u_char **, | ||
44 | size_t *, u_char *, size_t, u_int); | ||
45 | |||
46 | /* | ||
47 | * stubs for the server side implementation of kex. | ||
48 | * disable privsep so our stubs will never be called. | ||
49 | */ | ||
50 | int use_privsep = 0; | ||
51 | int mm_sshkey_sign(struct sshkey *, u_char **, u_int *, | ||
52 | u_char *, u_int, u_int); | ||
53 | DH *mm_choose_dh(int, int, int); | ||
54 | |||
55 | /* Define these two variables here so that they are part of the library */ | ||
56 | u_char *session_id2 = NULL; | ||
57 | u_int session_id2_len = 0; | ||
58 | |||
59 | int | ||
60 | mm_sshkey_sign(struct sshkey *key, u_char **sigp, u_int *lenp, | ||
61 | u_char *data, u_int datalen, u_int compat) | ||
62 | { | ||
63 | return (-1); | ||
64 | } | ||
65 | |||
66 | DH * | ||
67 | mm_choose_dh(int min, int nbits, int max) | ||
68 | { | ||
69 | return (NULL); | ||
70 | } | ||
71 | |||
72 | /* API */ | ||
73 | |||
74 | int | ||
75 | ssh_init(struct ssh **sshp, int is_server, struct kex_params *kex_params) | ||
76 | { | ||
77 | char *myproposal[PROPOSAL_MAX] = { KEX_CLIENT }; | ||
78 | struct ssh *ssh; | ||
79 | char **proposal; | ||
80 | static int called; | ||
81 | int r; | ||
82 | |||
83 | if (!called) { | ||
84 | OpenSSL_add_all_algorithms(); | ||
85 | called = 1; | ||
86 | } | ||
87 | |||
88 | ssh = ssh_packet_set_connection(NULL, -1, -1); | ||
89 | if (is_server) | ||
90 | ssh_packet_set_server(ssh); | ||
91 | |||
92 | /* Initialize key exchange */ | ||
93 | proposal = kex_params ? kex_params->proposal : myproposal; | ||
94 | if ((r = kex_new(ssh, proposal, &ssh->kex)) != 0) { | ||
95 | ssh_free(ssh); | ||
96 | return r; | ||
97 | } | ||
98 | ssh->kex->server = is_server; | ||
99 | if (is_server) { | ||
100 | #ifdef WITH_OPENSSL | ||
101 | ssh->kex->kex[KEX_DH_GRP1_SHA1] = kexdh_server; | ||
102 | ssh->kex->kex[KEX_DH_GRP14_SHA1] = kexdh_server; | ||
103 | ssh->kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; | ||
104 | ssh->kex->kex[KEX_DH_GEX_SHA256] = kexgex_server; | ||
105 | ssh->kex->kex[KEX_ECDH_SHA2] = kexecdh_server; | ||
106 | #endif /* WITH_OPENSSL */ | ||
107 | ssh->kex->kex[KEX_C25519_SHA256] = kexc25519_server; | ||
108 | ssh->kex->load_host_public_key=&_ssh_host_public_key; | ||
109 | ssh->kex->load_host_private_key=&_ssh_host_private_key; | ||
110 | ssh->kex->sign=&_ssh_host_key_sign; | ||
111 | } else { | ||
112 | #ifdef WITH_OPENSSL | ||
113 | ssh->kex->kex[KEX_DH_GRP1_SHA1] = kexdh_client; | ||
114 | ssh->kex->kex[KEX_DH_GRP14_SHA1] = kexdh_client; | ||
115 | ssh->kex->kex[KEX_DH_GEX_SHA1] = kexgex_client; | ||
116 | ssh->kex->kex[KEX_DH_GEX_SHA256] = kexgex_client; | ||
117 | ssh->kex->kex[KEX_ECDH_SHA2] = kexecdh_client; | ||
118 | #endif /* WITH_OPENSSL */ | ||
119 | ssh->kex->kex[KEX_C25519_SHA256] = kexc25519_client; | ||
120 | ssh->kex->verify_host_key =&_ssh_verify_host_key; | ||
121 | } | ||
122 | *sshp = ssh; | ||
123 | return 0; | ||
124 | } | ||
125 | |||
126 | void | ||
127 | ssh_free(struct ssh *ssh) | ||
128 | { | ||
129 | struct key_entry *k; | ||
130 | |||
131 | ssh_packet_close(ssh); | ||
132 | /* | ||
133 | * we've only created the public keys variants in case we | ||
134 | * are a acting as a server. | ||
135 | */ | ||
136 | while ((k = TAILQ_FIRST(&ssh->public_keys)) != NULL) { | ||
137 | TAILQ_REMOVE(&ssh->public_keys, k, next); | ||
138 | if (ssh->kex && ssh->kex->server) | ||
139 | sshkey_free(k->key); | ||
140 | free(k); | ||
141 | } | ||
142 | while ((k = TAILQ_FIRST(&ssh->private_keys)) != NULL) { | ||
143 | TAILQ_REMOVE(&ssh->private_keys, k, next); | ||
144 | free(k); | ||
145 | } | ||
146 | if (ssh->kex) | ||
147 | kex_free(ssh->kex); | ||
148 | free(ssh); | ||
149 | } | ||
150 | |||
151 | void | ||
152 | ssh_set_app_data(struct ssh *ssh, void *app_data) | ||
153 | { | ||
154 | ssh->app_data = app_data; | ||
155 | } | ||
156 | |||
157 | void * | ||
158 | ssh_get_app_data(struct ssh *ssh) | ||
159 | { | ||
160 | return ssh->app_data; | ||
161 | } | ||
162 | |||
163 | /* Returns < 0 on error, 0 otherwise */ | ||
164 | int | ||
165 | ssh_add_hostkey(struct ssh *ssh, struct sshkey *key) | ||
166 | { | ||
167 | struct sshkey *pubkey = NULL; | ||
168 | struct key_entry *k = NULL, *k_prv = NULL; | ||
169 | int r; | ||
170 | |||
171 | if (ssh->kex->server) { | ||
172 | if ((r = sshkey_from_private(key, &pubkey)) != 0) | ||
173 | return r; | ||
174 | if ((k = malloc(sizeof(*k))) == NULL || | ||
175 | (k_prv = malloc(sizeof(*k_prv))) == NULL) { | ||
176 | free(k); | ||
177 | sshkey_free(pubkey); | ||
178 | return SSH_ERR_ALLOC_FAIL; | ||
179 | } | ||
180 | k_prv->key = key; | ||
181 | TAILQ_INSERT_TAIL(&ssh->private_keys, k_prv, next); | ||
182 | |||
183 | /* add the public key, too */ | ||
184 | k->key = pubkey; | ||
185 | TAILQ_INSERT_TAIL(&ssh->public_keys, k, next); | ||
186 | r = 0; | ||
187 | } else { | ||
188 | if ((k = malloc(sizeof(*k))) == NULL) | ||
189 | return SSH_ERR_ALLOC_FAIL; | ||
190 | k->key = key; | ||
191 | TAILQ_INSERT_TAIL(&ssh->public_keys, k, next); | ||
192 | r = 0; | ||
193 | } | ||
194 | |||
195 | return r; | ||
196 | } | ||
197 | |||
198 | int | ||
199 | ssh_set_verify_host_key_callback(struct ssh *ssh, | ||
200 | int (*cb)(struct sshkey *, struct ssh *)) | ||
201 | { | ||
202 | if (cb == NULL || ssh->kex == NULL) | ||
203 | return SSH_ERR_INVALID_ARGUMENT; | ||
204 | |||
205 | ssh->kex->verify_host_key = cb; | ||
206 | |||
207 | return 0; | ||
208 | } | ||
209 | |||
210 | int | ||
211 | ssh_input_append(struct ssh *ssh, const u_char *data, size_t len) | ||
212 | { | ||
213 | return sshbuf_put(ssh_packet_get_input(ssh), data, len); | ||
214 | } | ||
215 | |||
216 | int | ||
217 | ssh_packet_next(struct ssh *ssh, u_char *typep) | ||
218 | { | ||
219 | int r; | ||
220 | u_int32_t seqnr; | ||
221 | u_char type; | ||
222 | |||
223 | /* | ||
224 | * Try to read a packet. Return SSH_MSG_NONE if no packet or not | ||
225 | * enough data. | ||
226 | */ | ||
227 | *typep = SSH_MSG_NONE; | ||
228 | if (ssh->kex->client_version_string == NULL || | ||
229 | ssh->kex->server_version_string == NULL) | ||
230 | return _ssh_exchange_banner(ssh); | ||
231 | /* | ||
232 | * If we enough data and a dispatch function then | ||
233 | * call the function and get the next packet. | ||
234 | * Otherwise return the packet type to the caller so it | ||
235 | * can decide how to go on. | ||
236 | * | ||
237 | * We will only call the dispatch function for: | ||
238 | * 20-29 Algorithm negotiation | ||
239 | * 30-49 Key exchange method specific (numbers can be reused for | ||
240 | * different authentication methods) | ||
241 | */ | ||
242 | for (;;) { | ||
243 | if ((r = ssh_packet_read_poll2(ssh, &type, &seqnr)) != 0) | ||
244 | return r; | ||
245 | if (type > 0 && type < DISPATCH_MAX && | ||
246 | type >= SSH2_MSG_KEXINIT && type <= SSH2_MSG_TRANSPORT_MAX && | ||
247 | ssh->dispatch[type] != NULL) { | ||
248 | if ((r = (*ssh->dispatch[type])(type, seqnr, ssh)) != 0) | ||
249 | return r; | ||
250 | } else { | ||
251 | *typep = type; | ||
252 | return 0; | ||
253 | } | ||
254 | } | ||
255 | } | ||
256 | |||
257 | const u_char * | ||
258 | ssh_packet_payload(struct ssh *ssh, size_t *lenp) | ||
259 | { | ||
260 | return sshpkt_ptr(ssh, lenp); | ||
261 | } | ||
262 | |||
263 | int | ||
264 | ssh_packet_put(struct ssh *ssh, int type, const u_char *data, size_t len) | ||
265 | { | ||
266 | int r; | ||
267 | |||
268 | if ((r = sshpkt_start(ssh, type)) != 0 || | ||
269 | (r = sshpkt_put(ssh, data, len)) != 0 || | ||
270 | (r = sshpkt_send(ssh)) != 0) | ||
271 | return r; | ||
272 | return 0; | ||
273 | } | ||
274 | |||
275 | const u_char * | ||
276 | ssh_output_ptr(struct ssh *ssh, size_t *len) | ||
277 | { | ||
278 | struct sshbuf *output = ssh_packet_get_output(ssh); | ||
279 | |||
280 | *len = sshbuf_len(output); | ||
281 | return sshbuf_ptr(output); | ||
282 | } | ||
283 | |||
284 | int | ||
285 | ssh_output_consume(struct ssh *ssh, size_t len) | ||
286 | { | ||
287 | return sshbuf_consume(ssh_packet_get_output(ssh), len); | ||
288 | } | ||
289 | |||
290 | int | ||
291 | ssh_output_space(struct ssh *ssh, size_t len) | ||
292 | { | ||
293 | return (0 == sshbuf_check_reserve(ssh_packet_get_output(ssh), len)); | ||
294 | } | ||
295 | |||
296 | int | ||
297 | ssh_input_space(struct ssh *ssh, size_t len) | ||
298 | { | ||
299 | return (0 == sshbuf_check_reserve(ssh_packet_get_input(ssh), len)); | ||
300 | } | ||
301 | |||
302 | /* Read other side's version identification. */ | ||
303 | int | ||
304 | _ssh_read_banner(struct ssh *ssh, char **bannerp) | ||
305 | { | ||
306 | struct sshbuf *input; | ||
307 | const char *s; | ||
308 | char buf[256], remote_version[256]; /* must be same size! */ | ||
309 | const char *mismatch = "Protocol mismatch.\r\n"; | ||
310 | int r, remote_major, remote_minor; | ||
311 | size_t i, n, j, len; | ||
312 | |||
313 | *bannerp = NULL; | ||
314 | input = ssh_packet_get_input(ssh); | ||
315 | len = sshbuf_len(input); | ||
316 | s = (const char *)sshbuf_ptr(input); | ||
317 | for (j = n = 0;;) { | ||
318 | for (i = 0; i < sizeof(buf) - 1; i++) { | ||
319 | if (j >= len) | ||
320 | return (0); | ||
321 | buf[i] = s[j++]; | ||
322 | if (buf[i] == '\r') { | ||
323 | buf[i] = '\n'; | ||
324 | buf[i + 1] = 0; | ||
325 | continue; /**XXX wait for \n */ | ||
326 | } | ||
327 | if (buf[i] == '\n') { | ||
328 | buf[i + 1] = 0; | ||
329 | break; | ||
330 | } | ||
331 | } | ||
332 | buf[sizeof(buf) - 1] = 0; | ||
333 | if (strncmp(buf, "SSH-", 4) == 0) | ||
334 | break; | ||
335 | debug("ssh_exchange_identification: %s", buf); | ||
336 | if (ssh->kex->server || ++n > 65536) { | ||
337 | if ((r = sshbuf_put(ssh_packet_get_output(ssh), | ||
338 | mismatch, strlen(mismatch))) != 0) | ||
339 | return r; | ||
340 | return SSH_ERR_NO_PROTOCOL_VERSION; | ||
341 | } | ||
342 | } | ||
343 | if ((r = sshbuf_consume(input, j)) != 0) | ||
344 | return r; | ||
345 | |||
346 | /* | ||
347 | * Check that the versions match. In future this might accept | ||
348 | * several versions and set appropriate flags to handle them. | ||
349 | */ | ||
350 | if (sscanf(buf, "SSH-%d.%d-%[^\n]\n", | ||
351 | &remote_major, &remote_minor, remote_version) != 3) | ||
352 | return SSH_ERR_INVALID_FORMAT; | ||
353 | debug("Remote protocol version %d.%d, remote software version %.100s", | ||
354 | remote_major, remote_minor, remote_version); | ||
355 | |||
356 | ssh->compat = compat_datafellows(remote_version); | ||
357 | if (remote_major == 1 && remote_minor == 99) { | ||
358 | remote_major = 2; | ||
359 | remote_minor = 0; | ||
360 | } | ||
361 | if (remote_major != 2) | ||
362 | return SSH_ERR_PROTOCOL_MISMATCH; | ||
363 | enable_compat20(); | ||
364 | chop(buf); | ||
365 | debug("Remote version string %.100s", buf); | ||
366 | if ((*bannerp = strdup(buf)) == NULL) | ||
367 | return SSH_ERR_ALLOC_FAIL; | ||
368 | return 0; | ||
369 | } | ||
370 | |||
371 | /* Send our own protocol version identification. */ | ||
372 | int | ||
373 | _ssh_send_banner(struct ssh *ssh, char **bannerp) | ||
374 | { | ||
375 | char buf[256]; | ||
376 | int r; | ||
377 | |||
378 | snprintf(buf, sizeof buf, "SSH-2.0-%.100s\r\n", SSH_VERSION); | ||
379 | if ((r = sshbuf_put(ssh_packet_get_output(ssh), buf, strlen(buf))) != 0) | ||
380 | return r; | ||
381 | chop(buf); | ||
382 | debug("Local version string %.100s", buf); | ||
383 | if ((*bannerp = strdup(buf)) == NULL) | ||
384 | return SSH_ERR_ALLOC_FAIL; | ||
385 | return 0; | ||
386 | } | ||
387 | |||
388 | int | ||
389 | _ssh_exchange_banner(struct ssh *ssh) | ||
390 | { | ||
391 | struct kex *kex = ssh->kex; | ||
392 | int r; | ||
393 | |||
394 | /* | ||
395 | * if _ssh_read_banner() cannot parse a full version string | ||
396 | * it will return NULL and we end up calling it again. | ||
397 | */ | ||
398 | |||
399 | r = 0; | ||
400 | if (kex->server) { | ||
401 | if (kex->server_version_string == NULL) | ||
402 | r = _ssh_send_banner(ssh, &kex->server_version_string); | ||
403 | if (r == 0 && | ||
404 | kex->server_version_string != NULL && | ||
405 | kex->client_version_string == NULL) | ||
406 | r = _ssh_read_banner(ssh, &kex->client_version_string); | ||
407 | } else { | ||
408 | if (kex->server_version_string == NULL) | ||
409 | r = _ssh_read_banner(ssh, &kex->server_version_string); | ||
410 | if (r == 0 && | ||
411 | kex->server_version_string != NULL && | ||
412 | kex->client_version_string == NULL) | ||
413 | r = _ssh_send_banner(ssh, &kex->client_version_string); | ||
414 | } | ||
415 | if (r != 0) | ||
416 | return r; | ||
417 | /* start initial kex as soon as we have exchanged the banners */ | ||
418 | if (kex->server_version_string != NULL && | ||
419 | kex->client_version_string != NULL) { | ||
420 | if ((r = _ssh_order_hostkeyalgs(ssh)) != 0 || | ||
421 | (r = kex_send_kexinit(ssh)) != 0) | ||
422 | return r; | ||
423 | } | ||
424 | return 0; | ||
425 | } | ||
426 | |||
427 | struct sshkey * | ||
428 | _ssh_host_public_key(int type, struct ssh *ssh) | ||
429 | { | ||
430 | struct key_entry *k; | ||
431 | |||
432 | debug3("%s: need %d", __func__, type); | ||
433 | TAILQ_FOREACH(k, &ssh->public_keys, next) { | ||
434 | debug3("%s: check %s", __func__, sshkey_type(k->key)); | ||
435 | if (k->key->type == type) | ||
436 | return (k->key); | ||
437 | } | ||
438 | return (NULL); | ||
439 | } | ||
440 | |||
441 | struct sshkey * | ||
442 | _ssh_host_private_key(int type, struct ssh *ssh) | ||
443 | { | ||
444 | struct key_entry *k; | ||
445 | |||
446 | debug3("%s: need %d", __func__, type); | ||
447 | TAILQ_FOREACH(k, &ssh->private_keys, next) { | ||
448 | debug3("%s: check %s", __func__, sshkey_type(k->key)); | ||
449 | if (k->key->type == type) | ||
450 | return (k->key); | ||
451 | } | ||
452 | return (NULL); | ||
453 | } | ||
454 | |||
455 | int | ||
456 | _ssh_verify_host_key(struct sshkey *hostkey, struct ssh *ssh) | ||
457 | { | ||
458 | struct key_entry *k; | ||
459 | |||
460 | debug3("%s: need %s", __func__, sshkey_type(hostkey)); | ||
461 | TAILQ_FOREACH(k, &ssh->public_keys, next) { | ||
462 | debug3("%s: check %s", __func__, sshkey_type(k->key)); | ||
463 | if (sshkey_equal_public(hostkey, k->key)) | ||
464 | return (0); /* ok */ | ||
465 | } | ||
466 | return (-1); /* failed */ | ||
467 | } | ||
468 | |||
469 | /* offer hostkey algorithms in kexinit depending on registered keys */ | ||
470 | int | ||
471 | _ssh_order_hostkeyalgs(struct ssh *ssh) | ||
472 | { | ||
473 | struct key_entry *k; | ||
474 | char *orig, *avail, *oavail = NULL, *alg, *replace = NULL; | ||
475 | char **proposal; | ||
476 | size_t maxlen; | ||
477 | int ktype, r; | ||
478 | |||
479 | /* XXX we de-serialize ssh->kex->my, modify it, and change it */ | ||
480 | if ((r = kex_buf2prop(ssh->kex->my, NULL, &proposal)) != 0) | ||
481 | return r; | ||
482 | orig = proposal[PROPOSAL_SERVER_HOST_KEY_ALGS]; | ||
483 | if ((oavail = avail = strdup(orig)) == NULL) { | ||
484 | r = SSH_ERR_ALLOC_FAIL; | ||
485 | goto out; | ||
486 | } | ||
487 | maxlen = strlen(avail) + 1; | ||
488 | if ((replace = calloc(1, maxlen)) == NULL) { | ||
489 | r = SSH_ERR_ALLOC_FAIL; | ||
490 | goto out; | ||
491 | } | ||
492 | *replace = '\0'; | ||
493 | while ((alg = strsep(&avail, ",")) && *alg != '\0') { | ||
494 | if ((ktype = sshkey_type_from_name(alg)) == KEY_UNSPEC) | ||
495 | continue; | ||
496 | TAILQ_FOREACH(k, &ssh->public_keys, next) { | ||
497 | if (k->key->type == ktype || | ||
498 | (sshkey_is_cert(k->key) && k->key->type == | ||
499 | sshkey_type_plain(ktype))) { | ||
500 | if (*replace != '\0') | ||
501 | strlcat(replace, ",", maxlen); | ||
502 | strlcat(replace, alg, maxlen); | ||
503 | break; | ||
504 | } | ||
505 | } | ||
506 | } | ||
507 | if (*replace != '\0') { | ||
508 | debug2("%s: orig/%d %s", __func__, ssh->kex->server, orig); | ||
509 | debug2("%s: replace/%d %s", __func__, ssh->kex->server, replace); | ||
510 | free(orig); | ||
511 | proposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = replace; | ||
512 | replace = NULL; /* owned by proposal */ | ||
513 | r = kex_prop2buf(ssh->kex->my, proposal); | ||
514 | } | ||
515 | out: | ||
516 | free(oavail); | ||
517 | free(replace); | ||
518 | kex_prop_free(proposal); | ||
519 | return r; | ||
520 | } | ||
521 | |||
522 | int | ||
523 | _ssh_host_key_sign(struct sshkey *privkey, struct sshkey *pubkey, | ||
524 | u_char **signature, size_t *slen, u_char *data, size_t dlen, u_int compat) | ||
525 | { | ||
526 | return sshkey_sign(privkey, signature, slen, data, dlen, compat); | ||
527 | } | ||