diff options
Diffstat (limited to 'regress/misc/kexfuzz')
-rw-r--r-- | regress/misc/kexfuzz/Makefile | 78 | ||||
-rw-r--r-- | regress/misc/kexfuzz/README | 28 | ||||
-rw-r--r-- | regress/misc/kexfuzz/kexfuzz.c | 410 |
3 files changed, 516 insertions, 0 deletions
diff --git a/regress/misc/kexfuzz/Makefile b/regress/misc/kexfuzz/Makefile new file mode 100644 index 000000000..3018b632f --- /dev/null +++ b/regress/misc/kexfuzz/Makefile | |||
@@ -0,0 +1,78 @@ | |||
1 | # $OpenBSD: Makefile,v 1.1 2016/03/04 02:30:37 djm Exp $ | ||
2 | |||
3 | .include <bsd.own.mk> | ||
4 | .include <bsd.obj.mk> | ||
5 | |||
6 | # XXX detect from ssh binary? | ||
7 | SSH1?= no | ||
8 | OPENSSL?= yes | ||
9 | |||
10 | PROG= kexfuzz | ||
11 | SRCS= kexfuzz.c | ||
12 | NOMAN= 1 | ||
13 | |||
14 | .if (${OPENSSL:L} == "yes") | ||
15 | CFLAGS+= -DWITH_OPENSSL | ||
16 | .else | ||
17 | # SSH v.1 requires OpenSSL. | ||
18 | SSH1= no | ||
19 | .endif | ||
20 | |||
21 | .if (${SSH1:L} == "yes") | ||
22 | CFLAGS+= -DWITH_SSH1 | ||
23 | .endif | ||
24 | |||
25 | # enable warnings | ||
26 | WARNINGS=Yes | ||
27 | |||
28 | DEBUG=-g | ||
29 | CFLAGS+= -fstack-protector-all | ||
30 | CDIAGFLAGS= -Wall | ||
31 | CDIAGFLAGS+= -Wextra | ||
32 | CDIAGFLAGS+= -Werror | ||
33 | CDIAGFLAGS+= -Wchar-subscripts | ||
34 | CDIAGFLAGS+= -Wcomment | ||
35 | CDIAGFLAGS+= -Wformat | ||
36 | CDIAGFLAGS+= -Wformat-security | ||
37 | CDIAGFLAGS+= -Wimplicit | ||
38 | CDIAGFLAGS+= -Winline | ||
39 | CDIAGFLAGS+= -Wmissing-declarations | ||
40 | CDIAGFLAGS+= -Wmissing-prototypes | ||
41 | CDIAGFLAGS+= -Wparentheses | ||
42 | CDIAGFLAGS+= -Wpointer-arith | ||
43 | CDIAGFLAGS+= -Wreturn-type | ||
44 | CDIAGFLAGS+= -Wshadow | ||
45 | CDIAGFLAGS+= -Wsign-compare | ||
46 | CDIAGFLAGS+= -Wstrict-aliasing | ||
47 | CDIAGFLAGS+= -Wstrict-prototypes | ||
48 | CDIAGFLAGS+= -Wswitch | ||
49 | CDIAGFLAGS+= -Wtrigraphs | ||
50 | CDIAGFLAGS+= -Wuninitialized | ||
51 | CDIAGFLAGS+= -Wunused | ||
52 | .if ${COMPILER_VERSION} == "gcc4" | ||
53 | CDIAGFLAGS+= -Wpointer-sign | ||
54 | CDIAGFLAGS+= -Wold-style-definition | ||
55 | .endif | ||
56 | |||
57 | SSHREL=../../../../../usr.bin/ssh | ||
58 | |||
59 | CFLAGS+=-I${.CURDIR}/${SSHREL} | ||
60 | |||
61 | .if exists(${.CURDIR}/${SSHREL}/lib/${__objdir}) | ||
62 | LDADD+=-L${.CURDIR}/${SSHREL}/lib/${__objdir} -lssh | ||
63 | DPADD+=${.CURDIR}/${SSHREL}/lib/${__objdir}/libssh.a | ||
64 | .else | ||
65 | LDADD+=-L${.CURDIR}/${SSHREL}/lib -lssh | ||
66 | DPADD+=${.CURDIR}/${SSHREL}/lib/libssh.a | ||
67 | .endif | ||
68 | |||
69 | LDADD+= -lutil -lz | ||
70 | DPADD+= ${LIBUTIL} ${LIBZ} | ||
71 | |||
72 | .if (${OPENSSL:L} == "yes") | ||
73 | LDADD+= -lcrypto | ||
74 | DPADD+= ${LIBCRYPTO} | ||
75 | .endif | ||
76 | |||
77 | .include <bsd.prog.mk> | ||
78 | |||
diff --git a/regress/misc/kexfuzz/README b/regress/misc/kexfuzz/README new file mode 100644 index 000000000..8b215b5bf --- /dev/null +++ b/regress/misc/kexfuzz/README | |||
@@ -0,0 +1,28 @@ | |||
1 | This is a harness to help with fuzzing KEX. | ||
2 | |||
3 | To use it, you first set it to count packets in each direction: | ||
4 | |||
5 | ./kexfuzz -K diffie-hellman-group1-sha1 -k host_ed25519_key -c | ||
6 | S2C: 29 | ||
7 | C2S: 31 | ||
8 | |||
9 | Then get it to record a particular packet (in this case the 4th | ||
10 | packet from client->server): | ||
11 | |||
12 | ./kexfuzz -K diffie-hellman-group1-sha1 -k host_ed25519_key \ | ||
13 | -d -D C2S -i 3 -f packet_3 | ||
14 | |||
15 | Fuzz the packet somehow: | ||
16 | |||
17 | dd if=/dev/urandom of=packet_3 bs=32 count=1 # Just for example | ||
18 | |||
19 | Then re-run the key exchange substituting the modified packet in | ||
20 | its original sequence: | ||
21 | |||
22 | ./kexfuzz -K diffie-hellman-group1-sha1 -k host_ed25519_key \ | ||
23 | -r -D C2S -i 3 -f packet_3 | ||
24 | |||
25 | A comprehensive KEX fuzz run would fuzz every packet in both | ||
26 | directions for each key exchange type and every hostkey type. | ||
27 | This will take some time. | ||
28 | |||
diff --git a/regress/misc/kexfuzz/kexfuzz.c b/regress/misc/kexfuzz/kexfuzz.c new file mode 100644 index 000000000..2894d3a1e --- /dev/null +++ b/regress/misc/kexfuzz/kexfuzz.c | |||
@@ -0,0 +1,410 @@ | |||
1 | /* $OpenBSD: kexfuzz.c,v 1.1 2016/03/04 02:30:37 djm Exp $ */ | ||
2 | /* | ||
3 | * Fuzz harness for KEX code | ||
4 | * | ||
5 | * Placed in the public domain | ||
6 | */ | ||
7 | |||
8 | #include "includes.h" | ||
9 | |||
10 | #include <sys/types.h> | ||
11 | #include <sys/param.h> | ||
12 | #include <stdio.h> | ||
13 | #ifdef HAVE_STDINT_H | ||
14 | # include <stdint.h> | ||
15 | #endif | ||
16 | #include <stdlib.h> | ||
17 | #include <string.h> | ||
18 | #include <unistd.h> | ||
19 | #include <fcntl.h> | ||
20 | #ifdef HAVE_ERR_H | ||
21 | # include <err.h> | ||
22 | #endif | ||
23 | |||
24 | #include "ssherr.h" | ||
25 | #include "ssh_api.h" | ||
26 | #include "sshbuf.h" | ||
27 | #include "packet.h" | ||
28 | #include "myproposal.h" | ||
29 | #include "authfile.h" | ||
30 | |||
31 | struct ssh *active_state = NULL; /* XXX - needed for linking */ | ||
32 | |||
33 | void kex_tests(void); | ||
34 | static int do_debug = 0; | ||
35 | |||
36 | enum direction { S2C, C2S }; | ||
37 | |||
38 | static int | ||
39 | do_send_and_receive(struct ssh *from, struct ssh *to, int mydirection, | ||
40 | int *packet_count, int trigger_direction, int packet_index, | ||
41 | const char *dump_path, struct sshbuf *replace_data) | ||
42 | { | ||
43 | u_char type; | ||
44 | size_t len, olen; | ||
45 | const u_char *buf; | ||
46 | int r; | ||
47 | FILE *dumpfile; | ||
48 | |||
49 | for (;;) { | ||
50 | if ((r = ssh_packet_next(from, &type)) != 0) { | ||
51 | fprintf(stderr, "ssh_packet_next: %s\n", ssh_err(r)); | ||
52 | return r; | ||
53 | } | ||
54 | if (type != 0) | ||
55 | return 0; | ||
56 | buf = ssh_output_ptr(from, &len); | ||
57 | olen = len; | ||
58 | if (do_debug) { | ||
59 | printf("%s packet %d type %u len %zu:\n", | ||
60 | mydirection == S2C ? "s2c" : "c2s", | ||
61 | *packet_count, type, len); | ||
62 | sshbuf_dump_data(buf, len, stdout); | ||
63 | } | ||
64 | if (mydirection == trigger_direction && | ||
65 | packet_index == *packet_count) { | ||
66 | if (replace_data != NULL) { | ||
67 | buf = sshbuf_ptr(replace_data); | ||
68 | len = sshbuf_len(replace_data); | ||
69 | if (do_debug) { | ||
70 | printf("***** replaced packet " | ||
71 | "len %zu\n", len); | ||
72 | sshbuf_dump_data(buf, len, stdout); | ||
73 | } | ||
74 | } else if (dump_path != NULL) { | ||
75 | if ((dumpfile = fopen(dump_path, "w+")) == NULL) | ||
76 | err(1, "fopen %s", dump_path); | ||
77 | if (len != 0 && | ||
78 | fwrite(buf, len, 1, dumpfile) != 1) | ||
79 | err(1, "fwrite %s", dump_path); | ||
80 | if (do_debug) | ||
81 | printf("***** dumped packet " | ||
82 | "len %zu\n", len); | ||
83 | fclose(dumpfile); | ||
84 | exit(0); | ||
85 | } | ||
86 | } | ||
87 | (*packet_count)++; | ||
88 | if (len == 0) | ||
89 | return 0; | ||
90 | if ((r = ssh_input_append(to, buf, len)) != 0 || | ||
91 | (r = ssh_output_consume(from, olen)) != 0) | ||
92 | return r; | ||
93 | } | ||
94 | } | ||
95 | |||
96 | /* Minimal test_helper.c scaffholding to make this standalone */ | ||
97 | const char *in_test = NULL; | ||
98 | #define TEST_START(a) \ | ||
99 | do { \ | ||
100 | in_test = (a); \ | ||
101 | if (do_debug) \ | ||
102 | fprintf(stderr, "test %s starting\n", in_test); \ | ||
103 | } while (0) | ||
104 | #define TEST_DONE() \ | ||
105 | do { \ | ||
106 | if (do_debug) \ | ||
107 | fprintf(stderr, "test %s done\n", \ | ||
108 | in_test ? in_test : "???"); \ | ||
109 | in_test = NULL; \ | ||
110 | } while(0) | ||
111 | #define ASSERT_INT_EQ(a, b) \ | ||
112 | do { \ | ||
113 | if ((int)(a) != (int)(b)) { \ | ||
114 | fprintf(stderr, "%s %s:%d " \ | ||
115 | "%s (%d) != expected %s (%d)\n", \ | ||
116 | in_test ? in_test : "(none)", \ | ||
117 | __func__, __LINE__, #a, (int)(a), #b, (int)(b)); \ | ||
118 | exit(2); \ | ||
119 | } \ | ||
120 | } while (0) | ||
121 | #define ASSERT_INT_GE(a, b) \ | ||
122 | do { \ | ||
123 | if ((int)(a) < (int)(b)) { \ | ||
124 | fprintf(stderr, "%s %s:%d " \ | ||
125 | "%s (%d) < expected %s (%d)\n", \ | ||
126 | in_test ? in_test : "(none)", \ | ||
127 | __func__, __LINE__, #a, (int)(a), #b, (int)(b)); \ | ||
128 | exit(2); \ | ||
129 | } \ | ||
130 | } while (0) | ||
131 | #define ASSERT_PTR_NE(a, b) \ | ||
132 | do { \ | ||
133 | if ((a) == (b)) { \ | ||
134 | fprintf(stderr, "%s %s:%d " \ | ||
135 | "%s (%p) != expected %s (%p)\n", \ | ||
136 | in_test ? in_test : "(none)", \ | ||
137 | __func__, __LINE__, #a, (a), #b, (b)); \ | ||
138 | exit(2); \ | ||
139 | } \ | ||
140 | } while (0) | ||
141 | |||
142 | |||
143 | static void | ||
144 | run_kex(struct ssh *client, struct ssh *server, int *s2c, int *c2s, | ||
145 | int direction, int packet_index, | ||
146 | const char *dump_path, struct sshbuf *replace_data) | ||
147 | { | ||
148 | int r = 0; | ||
149 | |||
150 | while (!server->kex->done || !client->kex->done) { | ||
151 | if ((r = do_send_and_receive(server, client, S2C, s2c, | ||
152 | direction, packet_index, dump_path, replace_data))) | ||
153 | break; | ||
154 | if ((r = do_send_and_receive(client, server, C2S, c2s, | ||
155 | direction, packet_index, dump_path, replace_data))) | ||
156 | break; | ||
157 | } | ||
158 | if (do_debug) | ||
159 | printf("done: %s\n", ssh_err(r)); | ||
160 | ASSERT_INT_EQ(r, 0); | ||
161 | ASSERT_INT_EQ(server->kex->done, 1); | ||
162 | ASSERT_INT_EQ(client->kex->done, 1); | ||
163 | } | ||
164 | |||
165 | static void | ||
166 | do_kex_with_key(const char *kex, struct sshkey *prvkey, int *c2s, int *s2c, | ||
167 | int direction, int packet_index, | ||
168 | const char *dump_path, struct sshbuf *replace_data) | ||
169 | { | ||
170 | struct ssh *client = NULL, *server = NULL, *server2 = NULL; | ||
171 | struct sshkey *pubkey = NULL; | ||
172 | struct sshbuf *state; | ||
173 | struct kex_params kex_params; | ||
174 | char *myproposal[PROPOSAL_MAX] = { KEX_CLIENT }; | ||
175 | char *keyname = NULL; | ||
176 | |||
177 | TEST_START("sshkey_from_private"); | ||
178 | ASSERT_INT_EQ(sshkey_from_private(prvkey, &pubkey), 0); | ||
179 | TEST_DONE(); | ||
180 | |||
181 | TEST_START("ssh_init"); | ||
182 | memcpy(kex_params.proposal, myproposal, sizeof(myproposal)); | ||
183 | if (kex != NULL) | ||
184 | kex_params.proposal[PROPOSAL_KEX_ALGS] = strdup(kex); | ||
185 | keyname = strdup(sshkey_ssh_name(prvkey)); | ||
186 | ASSERT_PTR_NE(keyname, NULL); | ||
187 | kex_params.proposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = keyname; | ||
188 | ASSERT_INT_EQ(ssh_init(&client, 0, &kex_params), 0); | ||
189 | ASSERT_INT_EQ(ssh_init(&server, 1, &kex_params), 0); | ||
190 | ASSERT_PTR_NE(client, NULL); | ||
191 | ASSERT_PTR_NE(server, NULL); | ||
192 | TEST_DONE(); | ||
193 | |||
194 | TEST_START("ssh_add_hostkey"); | ||
195 | ASSERT_INT_EQ(ssh_add_hostkey(server, prvkey), 0); | ||
196 | ASSERT_INT_EQ(ssh_add_hostkey(client, pubkey), 0); | ||
197 | TEST_DONE(); | ||
198 | |||
199 | TEST_START("kex"); | ||
200 | run_kex(client, server, s2c, c2s, direction, packet_index, | ||
201 | dump_path, replace_data); | ||
202 | TEST_DONE(); | ||
203 | |||
204 | TEST_START("rekeying client"); | ||
205 | ASSERT_INT_EQ(kex_send_kexinit(client), 0); | ||
206 | run_kex(client, server, s2c, c2s, direction, packet_index, | ||
207 | dump_path, replace_data); | ||
208 | TEST_DONE(); | ||
209 | |||
210 | TEST_START("rekeying server"); | ||
211 | ASSERT_INT_EQ(kex_send_kexinit(server), 0); | ||
212 | run_kex(client, server, s2c, c2s, direction, packet_index, | ||
213 | dump_path, replace_data); | ||
214 | TEST_DONE(); | ||
215 | |||
216 | TEST_START("ssh_packet_get_state"); | ||
217 | state = sshbuf_new(); | ||
218 | ASSERT_PTR_NE(state, NULL); | ||
219 | ASSERT_INT_EQ(ssh_packet_get_state(server, state), 0); | ||
220 | ASSERT_INT_GE(sshbuf_len(state), 1); | ||
221 | TEST_DONE(); | ||
222 | |||
223 | TEST_START("ssh_packet_set_state"); | ||
224 | server2 = NULL; | ||
225 | ASSERT_INT_EQ(ssh_init(&server2, 1, NULL), 0); | ||
226 | ASSERT_PTR_NE(server2, NULL); | ||
227 | ASSERT_INT_EQ(ssh_add_hostkey(server2, prvkey), 0); | ||
228 | kex_free(server2->kex); /* XXX or should ssh_packet_set_state()? */ | ||
229 | ASSERT_INT_EQ(ssh_packet_set_state(server2, state), 0); | ||
230 | ASSERT_INT_EQ(sshbuf_len(state), 0); | ||
231 | sshbuf_free(state); | ||
232 | ASSERT_PTR_NE(server2->kex, NULL); | ||
233 | /* XXX we need to set the callbacks */ | ||
234 | server2->kex->kex[KEX_DH_GRP1_SHA1] = kexdh_server; | ||
235 | server2->kex->kex[KEX_DH_GRP14_SHA1] = kexdh_server; | ||
236 | server2->kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; | ||
237 | server2->kex->kex[KEX_DH_GEX_SHA256] = kexgex_server; | ||
238 | #ifdef OPENSSL_HAS_ECC | ||
239 | server2->kex->kex[KEX_ECDH_SHA2] = kexecdh_server; | ||
240 | #endif | ||
241 | server2->kex->kex[KEX_C25519_SHA256] = kexc25519_server; | ||
242 | server2->kex->load_host_public_key = server->kex->load_host_public_key; | ||
243 | server2->kex->load_host_private_key = server->kex->load_host_private_key; | ||
244 | server2->kex->sign = server->kex->sign; | ||
245 | TEST_DONE(); | ||
246 | |||
247 | TEST_START("rekeying server2"); | ||
248 | ASSERT_INT_EQ(kex_send_kexinit(server2), 0); | ||
249 | run_kex(client, server2, s2c, c2s, direction, packet_index, | ||
250 | dump_path, replace_data); | ||
251 | ASSERT_INT_EQ(kex_send_kexinit(client), 0); | ||
252 | run_kex(client, server2, s2c, c2s, direction, packet_index, | ||
253 | dump_path, replace_data); | ||
254 | TEST_DONE(); | ||
255 | |||
256 | TEST_START("cleanup"); | ||
257 | sshkey_free(pubkey); | ||
258 | ssh_free(client); | ||
259 | ssh_free(server); | ||
260 | ssh_free(server2); | ||
261 | free(keyname); | ||
262 | TEST_DONE(); | ||
263 | } | ||
264 | |||
265 | static void | ||
266 | usage(void) | ||
267 | { | ||
268 | fprintf(stderr, | ||
269 | "Usage: kexfuzz [-hcdrv] [-D direction] [-f data_file]\n" | ||
270 | " [-K kex_alg] [-k private_key] [-i packet_index]\n" | ||
271 | "\n" | ||
272 | "Options:\n" | ||
273 | " -h Display this help\n" | ||
274 | " -c Count packets sent during KEX\n" | ||
275 | " -d Dump mode: record KEX packet to data file\n" | ||
276 | " -r Replace mode: replace packet with data file\n" | ||
277 | " -v Turn on verbose logging\n" | ||
278 | " -D S2C|C2S Packet direction for replacement or dump\n" | ||
279 | " -f data_file Path to data file for replacement or dump\n" | ||
280 | " -K kex_alg Name of KEX algorithm to test (see below)\n" | ||
281 | " -k private_key Path to private key file\n" | ||
282 | " -i packet_index Index of packet to replace or dump (from 0)\n" | ||
283 | "\n" | ||
284 | "Available KEX algorithms: %s\n", kex_alg_list(' ')); | ||
285 | } | ||
286 | |||
287 | static void | ||
288 | badusage(const char *bad) | ||
289 | { | ||
290 | fprintf(stderr, "Invalid options\n"); | ||
291 | fprintf(stderr, "%s\n", bad); | ||
292 | usage(); | ||
293 | exit(1); | ||
294 | } | ||
295 | |||
296 | int | ||
297 | main(int argc, char **argv) | ||
298 | { | ||
299 | int ch, fd, r; | ||
300 | int count_flag = 0, dump_flag = 0, replace_flag = 0; | ||
301 | int packet_index = -1, direction = -1; | ||
302 | int s2c = 0, c2s = 0; /* packet counts */ | ||
303 | const char *kex = NULL, *kpath = NULL, *data_path = NULL; | ||
304 | struct sshkey *key = NULL; | ||
305 | struct sshbuf *replace_data = NULL; | ||
306 | |||
307 | setvbuf(stdout, NULL, _IONBF, 0); | ||
308 | while ((ch = getopt(argc, argv, "hcdrvD:f:K:k:i:")) != -1) { | ||
309 | switch (ch) { | ||
310 | case 'h': | ||
311 | usage(); | ||
312 | return 0; | ||
313 | case 'c': | ||
314 | count_flag = 1; | ||
315 | break; | ||
316 | case 'd': | ||
317 | dump_flag = 1; | ||
318 | break; | ||
319 | case 'r': | ||
320 | replace_flag = 1; | ||
321 | break; | ||
322 | case 'v': | ||
323 | do_debug = 1; | ||
324 | break; | ||
325 | |||
326 | case 'D': | ||
327 | if (strcasecmp(optarg, "s2c") == 0) | ||
328 | direction = S2C; | ||
329 | else if (strcasecmp(optarg, "c2s") == 0) | ||
330 | direction = C2S; | ||
331 | else | ||
332 | badusage("Invalid direction (-D)"); | ||
333 | break; | ||
334 | case 'f': | ||
335 | data_path = optarg; | ||
336 | break; | ||
337 | case 'K': | ||
338 | kex = optarg; | ||
339 | break; | ||
340 | case 'k': | ||
341 | kpath = optarg; | ||
342 | break; | ||
343 | case 'i': | ||
344 | packet_index = atoi(optarg); | ||
345 | if (packet_index < 0) | ||
346 | badusage("Invalid packet index"); | ||
347 | break; | ||
348 | default: | ||
349 | badusage("unsupported flag"); | ||
350 | } | ||
351 | } | ||
352 | argc -= optind; | ||
353 | argv += optind; | ||
354 | |||
355 | /* Must select a single mode */ | ||
356 | if ((count_flag + dump_flag + replace_flag) != 1) | ||
357 | badusage("Must select one mode: -c, -d or -r"); | ||
358 | /* KEX type is mandatory */ | ||
359 | if (kex == NULL || !kex_names_valid(kex) || strchr(kex, ',') != NULL) | ||
360 | badusage("Missing or invalid kex type (-K flag)"); | ||
361 | /* Valid key is mandatory */ | ||
362 | if (kpath == NULL) | ||
363 | badusage("Missing private key (-k flag)"); | ||
364 | if ((fd = open(kpath, O_RDONLY)) == -1) | ||
365 | err(1, "open %s", kpath); | ||
366 | if ((r = sshkey_load_private_type_fd(fd, KEY_UNSPEC, NULL, | ||
367 | &key, NULL)) != 0) | ||
368 | errx(1, "Unable to load key %s: %s", kpath, ssh_err(r)); | ||
369 | close(fd); | ||
370 | /* XXX check that it is a private key */ | ||
371 | /* XXX support certificates */ | ||
372 | if (key == NULL || key->type == KEY_UNSPEC || key->type == KEY_RSA1) | ||
373 | badusage("Invalid key file (-k flag)"); | ||
374 | |||
375 | /* Replace (fuzz) mode */ | ||
376 | if (replace_flag) { | ||
377 | if (packet_index == -1 || direction == -1 || data_path == NULL) | ||
378 | badusage("Replace (-r) mode must specify direction " | ||
379 | "(-D) packet index (-i) and data path (-f)"); | ||
380 | if ((fd = open(data_path, O_RDONLY)) == -1) | ||
381 | err(1, "open %s", data_path); | ||
382 | replace_data = sshbuf_new(); | ||
383 | if ((r = sshkey_load_file(fd, replace_data)) != 0) | ||
384 | errx(1, "read %s: %s", data_path, ssh_err(r)); | ||
385 | close(fd); | ||
386 | } | ||
387 | |||
388 | /* Dump mode */ | ||
389 | if (dump_flag) { | ||
390 | if (packet_index == -1 || direction == -1 || data_path == NULL) | ||
391 | badusage("Dump (-d) mode must specify direction " | ||
392 | "(-D), packet index (-i) and data path (-f)"); | ||
393 | } | ||
394 | |||
395 | /* Count mode needs no further flags */ | ||
396 | |||
397 | do_kex_with_key(kex, key, &c2s, &s2c, | ||
398 | direction, packet_index, | ||
399 | dump_flag ? data_path : NULL, | ||
400 | replace_flag ? replace_data : NULL); | ||
401 | sshkey_free(key); | ||
402 | sshbuf_free(replace_data); | ||
403 | |||
404 | if (count_flag) { | ||
405 | printf("S2C: %d\n", s2c); | ||
406 | printf("C2S: %d\n", c2s); | ||
407 | } | ||
408 | |||
409 | return 0; | ||
410 | } | ||