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
31struct ssh *active_state = NULL; /* XXX - needed for linking */
32
33void kex_tests(void);
34static int do_debug = 0;
35
36enum direction { S2C, C2S };
37
38static int
39do_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 */
97const 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
143static void
144run_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
165static void
166do_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
265static void
266usage(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
287static void
288badusage(const char *bad)
289{
290	fprintf(stderr, "Invalid options\n");
291	fprintf(stderr, "%s\n", bad);
292	usage();
293	exit(1);
294}
295
296int
297main(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}
411