Esempio n. 1
0
bool id_h(connection_t *c, const char *request) {
	char name[MAX_STRING_SIZE];

	if(sscanf(request, "%*d " MAX_STRING " %d.%d", name, &c->protocol_major, &c->protocol_minor) < 2) {
		logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ID", c->name,
			   c->hostname);
		return false;
	}

	/* Check if this is a control connection */

	if(name[0] == '^' && !strcmp(name + 1, controlcookie)) {
		c->status.control = true;
		c->allow_request = CONTROL;
		c->last_ping_time = now.tv_sec + 3600;

		free(c->name);
		c->name = xstrdup("<control>");

		return send_request(c, "%d %d %d", ACK, TINC_CTL_VERSION_CURRENT, getpid());
	}

	if(name[0] == '?') {
		if(!invitation_key) {
			logger(DEBUG_ALWAYS, LOG_ERR, "Got invitation from %s but we don't have an invitation key", c->hostname);
			return false;
		}

		c->ecdsa = ecdsa_set_base64_public_key(name + 1);
		if(!c->ecdsa) {
			logger(DEBUG_ALWAYS, LOG_ERR, "Got bad invitation from %s", c->hostname);
			return false;
		}

		c->status.invitation = true;
		char *mykey = ecdsa_get_base64_public_key(invitation_key);
		if(!mykey)
			return false;
		if(!send_request(c, "%d %s", ACK, mykey))
			return false;
		free(mykey);

		c->protocol_minor = 2;

		return sptps_start(&c->sptps, c, false, false, invitation_key, c->ecdsa, "tinc invitation", 15, send_meta_sptps, receive_invitation_sptps);
	}

	/* Check if identity is a valid name */

	if(!check_id(name)) {
		logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ID", c->name,
			   c->hostname, "invalid name");
		return false;
	}

	/* If this is an outgoing connection, make sure we are connected to the right host */

	if(c->outgoing) {
		if(strcmp(c->name, name)) {
			logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s is %s instead of %s", c->hostname, name,
				   c->name);
			return false;
		}
	} else {
		if(c->name)
			free(c->name);
		c->name = xstrdup(name);
	}

	/* Check if version matches */

	if(c->protocol_major != myself->connection->protocol_major) {
		logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s (%s) uses incompatible version %d.%d",
			c->name, c->hostname, c->protocol_major, c->protocol_minor);
		return false;
	}

	if(bypass_security) {
		if(!c->config_tree)
			init_configuration(&c->config_tree);
		c->allow_request = ACK;
		return send_ack(c);
	}

	if(!experimental)
		c->protocol_minor = 0;

	if(!c->config_tree) {
		init_configuration(&c->config_tree);

		if(!read_host_config(c->config_tree, c->name)) {
			logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s had unknown identity (%s)", c->hostname, c->name);
			return false;
		}

		if(experimental)
			read_ecdsa_public_key(c);
			/* Ignore failures if no key known yet */
	}

	if(c->protocol_minor && !ecdsa_active(c->ecdsa))
		c->protocol_minor = 1;

	/* Forbid version rollback for nodes whose Ed25519 key we know */

	if(ecdsa_active(c->ecdsa) && c->protocol_minor < 2) {
		logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s (%s) tries to roll back protocol version to %d.%d",
			c->name, c->hostname, c->protocol_major, c->protocol_minor);
		return false;
	}

	c->allow_request = METAKEY;

	if(c->protocol_minor >= 2) {
		c->allow_request = ACK;
		char label[25 + strlen(myself->name) + strlen(c->name)];

		if(c->outgoing)
			snprintf(label, sizeof label, "tinc TCP key expansion %s %s", myself->name, c->name);
		else
			snprintf(label, sizeof label, "tinc TCP key expansion %s %s", c->name, myself->name);

		return sptps_start(&c->sptps, c, c->outgoing, false, myself->connection->ecdsa, c->ecdsa, label, sizeof label, send_meta_sptps, receive_meta_sptps);
	} else {
		return send_metakey(c);
	}
}
Esempio n. 2
0
int main(int argc, char *argv[]) {
	program_name = argv[0];
	bool initiator = false;
	bool datagram = false;
#ifdef HAVE_LINUX
	bool tun = false;
#endif
	int packetloss = 0;
	int r;
	int option_index = 0;
	ecdsa_t *mykey = NULL, *hiskey = NULL;
	bool quit = false;

	while((r = getopt_long(argc, argv, "dqrtwL:W:v", long_options, &option_index)) != EOF) {
		switch (r) {
			case 0:   /* long option */
				break;

			case 'd': /* datagram mode */
				datagram = true;
				break;

			case 'q': /* close connection on EOF from stdin */
				quit = true;
				break;

			case 'r': /* read only */
				readonly = true;
				break;

			case 't': /* read only */
#ifdef HAVE_LINUX
				tun = true;
#else
				fprintf(stderr, "--tun is only supported on Linux.\n");
				usage();
				return 1;
#endif
				break;

			case 'w': /* write only */
				writeonly = true;
				break;

			case 'L': /* packet loss rate */
				packetloss = atoi(optarg);
				break;

			case 'W': /* replay window size */
				sptps_replaywin = atoi(optarg);
				break;

			case 'v': /* be verbose */
				verbose = true;
				break;

			case '?': /* wrong options */
				usage();
				return 1;

			case 1: /* help */
				usage();
				return 0;

			default:
				break;
		}
	}

	argc -= optind - 1;
	argv += optind - 1;

	if(argc < 4 || argc > 5) {
		fprintf(stderr, "Wrong number of arguments.\n");
		usage();
		return 1;
	}

	if(argc > 4)
		initiator = true;

	srand(time(NULL));

#ifdef HAVE_LINUX
	if(tun) {
		in = out = open("/dev/net/tun", O_RDWR | O_NONBLOCK);
		if(in < 0) {
			fprintf(stderr, "Could not open tun device: %s\n", strerror(errno));
			return 1;
		}
		struct ifreq ifr = {
			.ifr_flags = IFF_TUN
		};
		if(ioctl(in, TUNSETIFF, &ifr)) {
			fprintf(stderr, "Could not configure tun interface: %s\n", strerror(errno));
			return 1;
		}
		ifr.ifr_name[IFNAMSIZ - 1] = 0;
		fprintf(stderr, "Using tun interface %s\n", ifr.ifr_name);
	}
#endif

#ifdef HAVE_MINGW
	static struct WSAData wsa_state;
	if(WSAStartup(MAKEWORD(2, 2), &wsa_state))
		return 1;
#endif

	struct addrinfo *ai, hint;
	memset(&hint, 0, sizeof hint);

	hint.ai_family = AF_UNSPEC;
	hint.ai_socktype = datagram ? SOCK_DGRAM : SOCK_STREAM;
	hint.ai_protocol = datagram ? IPPROTO_UDP : IPPROTO_TCP;
	hint.ai_flags = initiator ? 0 : AI_PASSIVE;

	if(getaddrinfo(initiator ? argv[3] : NULL, initiator ? argv[4] : argv[3], &hint, &ai) || !ai) {
		fprintf(stderr, "getaddrinfo() failed: %s\n", sockstrerror(sockerrno));
		return 1;
	}

	int sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
	if(sock < 0) {
		fprintf(stderr, "Could not create socket: %s\n", sockstrerror(sockerrno));
		return 1;
	}

	int one = 1;
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof one);

	if(initiator) {
		if(connect(sock, ai->ai_addr, ai->ai_addrlen)) {
			fprintf(stderr, "Could not connect to peer: %s\n", sockstrerror(sockerrno));
			return 1;
		}
		fprintf(stderr, "Connected\n");
	} else {
		if(bind(sock, ai->ai_addr, ai->ai_addrlen)) {
			fprintf(stderr, "Could not bind socket: %s\n", sockstrerror(sockerrno));
			return 1;
		}

		if(!datagram) {
			if(listen(sock, 1)) {
				fprintf(stderr, "Could not listen on socket: %s\n", sockstrerror(sockerrno));
				return 1;
			}
			fprintf(stderr, "Listening...\n");

			sock = accept(sock, NULL, NULL);
			if(sock < 0) {
				fprintf(stderr, "Could not accept connection: %s\n", sockstrerror(sockerrno));
				return 1;
			}
		} else {
			fprintf(stderr, "Listening...\n");

			char buf[65536];
			struct sockaddr addr;
			socklen_t addrlen = sizeof addr;

			if(recvfrom(sock, buf, sizeof buf, MSG_PEEK, &addr, &addrlen) <= 0) {
				fprintf(stderr, "Could not read from socket: %s\n", sockstrerror(sockerrno));
				return 1;
			}

			if(connect(sock, &addr, addrlen)) {
				fprintf(stderr, "Could not accept connection: %s\n", sockstrerror(sockerrno));
				return 1;
			}
		}

		fprintf(stderr, "Connected\n");
	}

	crypto_init();

	FILE *fp = fopen(argv[1], "r");
	if(!fp) {
		fprintf(stderr, "Could not open %s: %s\n", argv[1], strerror(errno));
		return 1;
	}
	if(!(mykey = ecdsa_read_pem_private_key(fp)))
		return 1;
	fclose(fp);

	fp = fopen(argv[2], "r");
	if(!fp) {
		fprintf(stderr, "Could not open %s: %s\n", argv[2], strerror(errno));
		return 1;
	}
	if(!(hiskey = ecdsa_read_pem_public_key(fp)))
		return 1;
	fclose(fp);

	if(verbose)
		fprintf(stderr, "Keys loaded\n");

	sptps_t s;
	if(!sptps_start(&s, &sock, initiator, datagram, mykey, hiskey, "sptps_test", 10, send_data, receive_record))
		return 1;

	while(true) {
		if(writeonly && readonly)
			break;

		char buf[65535] = "";

		fd_set fds;
		FD_ZERO(&fds);
#ifndef HAVE_MINGW
		if(!readonly && s.instate)
			FD_SET(in, &fds);
#endif
		FD_SET(sock, &fds);
		if(select(sock + 1, &fds, NULL, NULL, NULL) <= 0)
			return 1;

		if(FD_ISSET(in, &fds)) {
			ssize_t len = read(in, buf, sizeof buf);
			if(len < 0) {
				fprintf(stderr, "Could not read from stdin: %s\n", strerror(errno));
				return 1;
			}
			if(len == 0) {
				if(quit)
					break;
				readonly = true;
				continue;
			}
			if(buf[0] == '#')
				s.outseqno = atoi(buf + 1);
			if(buf[0] == '^')
				sptps_send_record(&s, SPTPS_HANDSHAKE, NULL, 0);
			else if(buf[0] == '$') {
				sptps_force_kex(&s);
				if(len > 1)
					sptps_send_record(&s, 0, buf, len);
			} else
			if(!sptps_send_record(&s, buf[0] == '!' ? 1 : 0, buf, (len == 1 && buf[0] == '\n') ? 0 : buf[0] == '*' ? sizeof buf : len))
				return 1;
		}

		if(FD_ISSET(sock, &fds)) {
			ssize_t len = recv(sock, buf, sizeof buf, 0);
			if(len < 0) {
				fprintf(stderr, "Could not read from socket: %s\n", sockstrerror(sockerrno));
				return 1;
			}
			if(len == 0) {
				fprintf(stderr, "Connection terminated by peer.\n");
				break;
			}
			if(verbose) {
				char hex[len * 2 + 1];
				bin2hex(buf, hex, len);
				fprintf(stderr, "Received %d bytes of data:\n%s\n", (int)len, hex);
			}
			if(packetloss && (rand() % 100) < packetloss) {
				if(verbose)
					fprintf(stderr, "Dropped.\n");
				continue;
			}
			char *bufp = buf;
			while(len) {
				size_t done = sptps_receive_data(&s, bufp, len);
				if(!done) {
					if(!datagram)
						return 1;
				} else {
					break;
				}

				bufp += done;
				len -= done;
			}
		}
	}

	if(!sptps_stop(&s))
		return 1;

	return 0;
}
Esempio n. 3
0
int cmd_join(int argc, char *argv[]) {
	free(data);
	data = NULL;
	datalen = 0;

	if(argc > 2) {
		fprintf(stderr, "Too many arguments!\n");
		return 1;
	}

	// Make sure confbase exists and is accessible.
	if(!confbase_given && mkdir(confdir, 0755) && errno != EEXIST) {
		fprintf(stderr, "Could not create directory %s: %s\n", confdir, strerror(errno));
		return 1;
	}

	if(mkdir(confbase, 0777) && errno != EEXIST) {
		fprintf(stderr, "Could not create directory %s: %s\n", confbase, strerror(errno));
		return 1;
	}

	if(access(confbase, R_OK | W_OK | X_OK)) {
		fprintf(stderr, "No permission to write in directory %s: %s\n", confbase, strerror(errno));
		return 1;
	}

	// If a netname or explicit configuration directory is specified, check for an existing tinc.conf.
	if((netname || confbasegiven) && !access(tinc_conf, F_OK)) {
		fprintf(stderr, "Configuration file %s already exists!\n", tinc_conf);
		return 1;
	}

	// Either read the invitation from the command line or from stdin.
	char *invitation;

	if(argc > 1) {
		invitation = argv[1];
	} else {
		if(tty)
			fprintf(stderr, "Enter invitation URL: ");
		errno = EPIPE;
		if(!fgets(line, sizeof line, stdin)) {
			fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno));
			return false;
		}
		invitation = line;
	}

	// Parse the invitation URL.
	rstrip(line);

	char *slash = strchr(invitation, '/');
	if(!slash)
		goto invalid;

	*slash++ = 0;

	if(strlen(slash) != 48)
		goto invalid;

	char *address = invitation;
	char *port = NULL;
	if(*address == '[') {
		address++;
		char *bracket = strchr(address, ']');
		if(!bracket)
			goto invalid;
		*bracket = 0;
		if(bracket[1] == ':')
			port = bracket + 2;
	} else {
		port = strchr(address, ':');
		if(port)
			*port++ = 0;
	}

	if(!port || !*port)
		port = "655";

	if(!b64decode(slash, hash, 24) || !b64decode(slash + 24, cookie, 24))
		goto invalid;

	// Generate a throw-away key for the invitation.
	ecdsa_t *key = ecdsa_generate();
	if(!key)
		return 1;

	char *b64key = ecdsa_get_base64_public_key(key);

	// Connect to the tinc daemon mentioned in the URL.
	struct addrinfo *ai = str2addrinfo(address, port, SOCK_STREAM);
	if(!ai)
		return 1;

	struct addrinfo *aip = NULL;

next:
	if(!aip)
		aip = ai;
	else {
		aip = aip->ai_next;
		if(!aip)
			return 1;
	}

	sock = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol);
	if(sock <= 0) {
		fprintf(stderr, "Could not open socket: %s\n", strerror(errno));
		goto next;
	}

	if(connect(sock, aip->ai_addr, aip->ai_addrlen)) {
		char *addrstr, *portstr;
		sockaddr2str((sockaddr_t *)aip->ai_addr, &addrstr, &portstr);
		fprintf(stderr, "Could not connect to %s port %s: %s\n", addrstr, portstr, strerror(errno));
		free(addrstr);
		free(portstr);
		closesocket(sock);
		goto next;
	}

	fprintf(stderr, "Connected to %s port %s...\n", address, port);

	// Tell him we have an invitation, and give him our throw-away key.
	int len = snprintf(line, sizeof line, "0 ?%s %d.%d\n", b64key, PROT_MAJOR, PROT_MINOR);
	if(len <= 0 || len >= sizeof line)
		abort();

	if(!sendline(sock, "0 ?%s %d.%d", b64key, PROT_MAJOR, 1)) {
		fprintf(stderr, "Error sending request to %s port %s: %s\n", address, port, strerror(errno));
		closesocket(sock);
		goto next;
	}

	char hisname[4096] = "";
	int code, hismajor, hisminor = 0;

	if(!recvline(sock, line, sizeof line) || sscanf(line, "%d %s %d.%d", &code, hisname, &hismajor, &hisminor) < 3 || code != 0 || hismajor != PROT_MAJOR || !check_id(hisname) || !recvline(sock, line, sizeof line) || !rstrip(line) || sscanf(line, "%d ", &code) != 1 || code != ACK || strlen(line) < 3) {
		fprintf(stderr, "Cannot read greeting from peer\n");
		closesocket(sock);
		goto next;
	}

	// Check if the hash of the key he gave us matches the hash in the URL.
	char *fingerprint = line + 2;
	char hishash[64];
	if(sha512(fingerprint, strlen(fingerprint), hishash)) {
		fprintf(stderr, "Could not create digest\n%s\n", line + 2);
		return 1;
	}
	if(memcmp(hishash, hash, 18)) {
		fprintf(stderr, "Peer has an invalid key!\n%s\n", line + 2);
		return 1;

	}
	
	ecdsa_t *hiskey = ecdsa_set_base64_public_key(fingerprint);
	if(!hiskey)
		return 1;

	// Start an SPTPS session
	if(!sptps_start(&sptps, NULL, true, false, key, hiskey, "tinc invitation", 15, invitation_send, invitation_receive))
		return 1;

	// Feed rest of input buffer to SPTPS
	if(!sptps_receive_data(&sptps, buffer, blen))
		return 1;

	while((len = recv(sock, line, sizeof line, 0))) {
		if(len < 0) {
			if(errno == EINTR)
				continue;
			fprintf(stderr, "Error reading data from %s port %s: %s\n", address, port, strerror(errno));
			return 1;
		}

		char *p = line;
		while(len) {
			int done = sptps_receive_data(&sptps, p, len);
			if(!done)
				return 1;
			len -= done;
			p += done;
		}
	}
	
	sptps_stop(&sptps);
	ecdsa_free(hiskey);
	ecdsa_free(key);
	closesocket(sock);

	if(!success) {
		fprintf(stderr, "Connection closed by peer, invitation cancelled.\n");
		return 1;
	}

	return 0;

invalid:
	fprintf(stderr, "Invalid invitation URL.\n");
	return 1;
}
Esempio n. 4
0
int main(int argc, char *argv[]) {
	ecdsa_t *key1, *key2;
	ecdh_t *ecdh1, *ecdh2;
	sptps_t sptps1, sptps2;
	char buf1[4096], buf2[4096], buf3[4096];
	double duration = argc > 1 ? atof(argv[1]) : 10;

	crypto_init();

	randomize(buf1, sizeof buf1);
	randomize(buf2, sizeof buf2);
	randomize(buf3, sizeof buf3);

	// Key generation

	fprintf(stderr, "Generating keys for %lg seconds: ", duration);
	for(clock_start(); clock_countto(duration);)
		ecdsa_free(ecdsa_generate());
	fprintf(stderr, "%17.2lf op/s\n", rate);

	key1 = ecdsa_generate();
	key2 = ecdsa_generate();

	// Ed25519 signatures

	fprintf(stderr, "Ed25519 sign for %lg seconds: ", duration);
	for(clock_start(); clock_countto(duration);)
		if(!ecdsa_sign(key1, buf1, 256, buf2))
			return 1;
	fprintf(stderr, "%20.2lf op/s\n", rate);

	fprintf(stderr, "Ed25519 verify for %lg seconds: ", duration);
	for(clock_start(); clock_countto(duration);)
		if(!ecdsa_verify(key1, buf1, 256, buf2)) {
			fprintf(stderr, "Signature verification failed\n");
			return 1;
		}
	fprintf(stderr, "%18.2lf op/s\n", rate);

	ecdh1 = ecdh_generate_public(buf1);
	fprintf(stderr, "ECDH for %lg seconds: ", duration);
	for(clock_start(); clock_countto(duration);) {
		ecdh2 = ecdh_generate_public(buf2);
		if(!ecdh2)
			return 1;
		if(!ecdh_compute_shared(ecdh2, buf1, buf3))
			return 1;
	}
	fprintf(stderr, "%28.2lf op/s\n", rate);
	ecdh_free(ecdh1);

	// SPTPS authentication phase

	int fd[2];
	if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd)) {
		fprintf(stderr, "Could not create a UNIX socket pair: %s\n", sockstrerror(sockerrno));
		return 1;
	}

	struct pollfd pfd[2] = {{fd[0], POLLIN}, {fd[1], POLLIN}};

	fprintf(stderr, "SPTPS/TCP authenticate for %lg seconds: ", duration);
	for(clock_start(); clock_countto(duration);) {
		sptps_start(&sptps1, fd + 0, true, false, key1, key2, "sptps_speed", 11, send_data, receive_record);
		sptps_start(&sptps2, fd + 1, false, false, key2, key1, "sptps_speed", 11, send_data, receive_record);
		while(poll(pfd, 2, 0)) {
			if(pfd[0].revents)
				receive_data(&sptps1);
			if(pfd[1].revents)
				receive_data(&sptps2);
		}
		sptps_stop(&sptps1);
		sptps_stop(&sptps2);
	}
	fprintf(stderr, "%10.2lf op/s\n", rate * 2);

	// SPTPS data

	sptps_start(&sptps1, fd + 0, true, false, key1, key2, "sptps_speed", 11, send_data, receive_record);
	sptps_start(&sptps2, fd + 1, false, false, key2, key1, "sptps_speed", 11, send_data, receive_record);
	while(poll(pfd, 2, 0)) {
		if(pfd[0].revents)
			receive_data(&sptps1);
		if(pfd[1].revents)
			receive_data(&sptps2);
	}
	fprintf(stderr, "SPTPS/TCP transmit for %lg seconds: ", duration);
	for(clock_start(); clock_countto(duration);) {
		if(!sptps_send_record(&sptps1, 0, buf1, 1451))
			abort();
		receive_data(&sptps2);
	}
	rate *= 2 * 1451 * 8;
	if(rate > 1e9)
		fprintf(stderr, "%14.2lf Gbit/s\n", rate / 1e9);
	else if(rate > 1e6)
		fprintf(stderr, "%14.2lf Mbit/s\n", rate / 1e6);
	else if(rate > 1e3)
		fprintf(stderr, "%14.2lf kbit/s\n", rate / 1e3);
	sptps_stop(&sptps1);
	sptps_stop(&sptps2);

	// SPTPS datagram authentication phase

	close(fd[0]);
	close(fd[1]);

	if(socketpair(AF_UNIX, SOCK_DGRAM, 0, fd)) {
		fprintf(stderr, "Could not create a UNIX socket pair: %s\n", sockstrerror(sockerrno));
		return 1;
	}

	fprintf(stderr, "SPTPS/UDP authenticate for %lg seconds: ", duration);
	for(clock_start(); clock_countto(duration);) {
		sptps_start(&sptps1, fd + 0, true, true, key1, key2, "sptps_speed", 11, send_data, receive_record);
		sptps_start(&sptps2, fd + 1, false, true, key2, key1, "sptps_speed", 11, send_data, receive_record);
		while(poll(pfd, 2, 0)) {
			if(pfd[0].revents)
				receive_data(&sptps1);
			if(pfd[1].revents)
				receive_data(&sptps2);
		}
		sptps_stop(&sptps1);
		sptps_stop(&sptps2);
	}
	fprintf(stderr, "%10.2lf op/s\n", rate * 2);

	// SPTPS datagram data

	sptps_start(&sptps1, fd + 0, true, true, key1, key2, "sptps_speed", 11, send_data, receive_record);
	sptps_start(&sptps2, fd + 1, false, true, key2, key1, "sptps_speed", 11, send_data, receive_record);
	while(poll(pfd, 2, 0)) {
		if(pfd[0].revents)
			receive_data(&sptps1);
		if(pfd[1].revents)
			receive_data(&sptps2);
	}
	fprintf(stderr, "SPTPS/UDP transmit for %lg seconds: ", duration);
	for(clock_start(); clock_countto(duration);) {
		if(!sptps_send_record(&sptps1, 0, buf1, 1451))
			abort();
		receive_data(&sptps2);
	}
	rate *= 2 * 1451 * 8;
	if(rate > 1e9)
		fprintf(stderr, "%14.2lf Gbit/s\n", rate / 1e9);
	else if(rate > 1e6)
		fprintf(stderr, "%14.2lf Mbit/s\n", rate / 1e6);
	else if(rate > 1e3)
		fprintf(stderr, "%14.2lf kbit/s\n", rate / 1e3);
	sptps_stop(&sptps1);
	sptps_stop(&sptps2);

	// Clean up

	close(fd[0]);
	close(fd[1]);
	ecdsa_free(key1);
	ecdsa_free(key2);
	crypto_exit();

	return 0;
}