diff protocol.c @ 0:17cb7cdbb8be draft default tip

Working prototype
author Ivo Smits <Ivo@UCIS.nl>
date Fri, 07 Feb 2014 23:28:39 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/protocol.c	Fri Feb 07 23:28:39 2014 +0100
@@ -0,0 +1,335 @@
+/* Copyright 2014 Ivo Smits <Ivo@UCIS.nl>. All rights reserved.
+   Redistribution and use in source and binary forms, with or without modification, are
+   permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice, this list of
+      conditions and the following disclaimer.
+
+   2. Redistributions in binary form must reproduce the above copyright notice, this list
+      of conditions and the following disclaimer in the documentation and/or other materials
+      provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   The views and conclusions contained in the software and documentation are those of the
+   authors and should not be interpreted as representing official policies, either expressed
+   or implied, of Ivo Smits.*/
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sodium/crypto_box_curve25519xsalsa20poly1305.h>
+#include <sodium/crypto_scalarmult_curve25519.h>
+#include <sodium/crypto_hash_sha512.h>
+#include "include.h"
+
+static unsigned char workbuffer[2000];
+
+static int devurandomfd = -1;
+static bool randombytes(unsigned char* buffer, int len) {
+	if (devurandomfd == -1) devurandomfd = open("/dev/urandom", O_RDONLY);
+	if (devurandomfd == -1) return errorexit("could not open /dev/urandom");
+	while (len > 0) {
+		int got = read(devurandomfd, buffer, len);
+		if (got < 0) return errorexitp("could not read from /dev/urandom");
+		buffer += got;
+		len -= got;
+	}
+	return true;
+}
+
+static void dumphex(char* lbl, unsigned char* buffer, int len) {
+	fprintf(stderr, "%s: ", lbl);
+	for (; len > 0; len--, buffer++) fprintf(stderr, "%02x", *buffer);
+	fprintf(stderr, "\n");
+}
+
+static void nonceinc(unsigned char* nonce) {
+	int i;
+	for (i = 23; i >= 0 && (++nonce[i] == 0); i--);
+}
+
+static bool send_all(int socket, unsigned char* buffer, int length) {
+	while (length) {
+		int sent = write(socket, buffer, length);
+		if (sent <= 0) return errorexitp("socket write failed");
+		buffer += sent;
+		length -= sent;
+	}
+	return true;
+}
+static bool send_packet(connection_context* context, unsigned char* buffer, int length, bool controlflag) {
+	fprintf(stderr, "Send packet (size=%d type=%d control=%d encrypt=%d)\n", length, buffer[0], controlflag, context->send_encrypted);
+	if (context->send_encrypted) {
+		if (length + 32 > sizeof(workbuffer)) return errorexit("packet too big for encryption buffer");
+		memmove(workbuffer + 32, buffer, length);
+		memset(workbuffer, 0, 32);
+		crypto_box_curve25519xsalsa20poly1305_afternm(workbuffer, workbuffer, length + 32, context->send_nonce, context->send_key);
+		nonceinc(context->send_nonce);
+		buffer = workbuffer + 16;
+		length += 16;
+	}
+	if (length > 0x7FFF) return errorexit("packet too big for framing");
+	unsigned char lbuf[2];
+	length = (length & 0x7FFF) | (controlflag ? 0x8000 : 0);
+	lbuf[0] = (length >> 8) & 0xFF;
+	lbuf[1] = length & 0xFF;
+	if (!send_all(context->send_socket, lbuf, 2)) return false;
+	if (!send_all(context->send_socket, buffer, length & 0x7FFF)) return false;
+	return true;
+}
+static bool send_command_ack(connection_context* context, unsigned char code) {
+	workbuffer[0] = code;
+	return send_packet(context, workbuffer, 1, true);
+}
+
+
+static bool check_tunnel_ready(connection_context* context) {
+	if (context->local_tunnelready) return true;
+	if (context->require_key_authentication) return true;
+	if (context->require_password_authentication) return true;
+	if (context->require_encryption && !context->recv_encrypted) return true;
+	if (!send_command_ack(context, 0)) return false;
+	context->local_tunnelready = true;
+	return true;
+}
+
+static bool process_network_packet(connection_context* context, unsigned char* buffer, int pktlen, int controlflag);
+bool connection_read(connection_context* context) {
+	int got = read(context->recv_socket, context->recv_buffer + context->recv_offset, sizeof(context->recv_buffer) - context->recv_offset);
+	if (got < 0) return errorexitp("read failure on socket");
+	context->recv_offset += got;
+	while (context->recv_offset >= 2) {
+		int pktlen = ((unsigned char)context->recv_buffer[0] << 8) | (unsigned char)context->recv_buffer[1];
+		int controlflag = pktlen & 0x8000;
+		pktlen &= 0x7FFF;
+		if (pktlen > sizeof(context->recv_buffer)) return errorexit("received packet too big for buffer");
+		if (context->recv_offset < pktlen + 2) break;
+		if (!process_network_packet(context, context->recv_buffer + 2, pktlen, !!controlflag)) return false;
+		context->recv_offset -= 2 + pktlen;
+		memmove(context->recv_buffer, context->recv_buffer + 2 + pktlen, context->recv_offset);
+	}
+	if (!context->local_tunnelready) if (!check_tunnel_ready(context)) return false;
+	return true;
+}
+static bool send_start_crypt_auth(connection_context* context, const unsigned char* key) {
+	if (!context->key_updated) return context->startcryptauthsent && !key;
+	if (key) {
+		memcpy(context->local_seckey_next, key, 32);
+	} else {
+		randombytes(context->local_seckey_next, 32);
+	}
+	workbuffer[0] = 1;
+	crypto_scalarmult_curve25519_base(workbuffer + 1, context->local_seckey_next);
+	memcpy(workbuffer + 1 + 32, context->nonce_next, 24);
+	if (!send_packet(context, workbuffer, 1 + 32 + 24, true)) return false;
+	crypto_box_curve25519xsalsa20poly1305_beforenm(context->send_key, context->remote_pubkey, context->local_seckey_next);
+	memcpy(context->send_nonce + 12, context->nonce_next + 12, 12);
+	context->send_encrypted = true;
+	context->startcryptauthsent = true;
+	context->key_updated = false;
+	return true;
+}
+static bool begin_update_key(connection_context* context, char* seckey) {
+	if (!context->key_updated) return !seckey;
+	if (seckey) {
+		memcpy(context->local_seckey_next, seckey, 32);
+	} else {
+		randombytes(context->local_seckey_next, 32);
+	}
+	workbuffer[0] = 3;
+	crypto_scalarmult_curve25519_base(workbuffer + 1, context->local_seckey_next);
+	if (!send_packet(context, workbuffer, 1 + 32, true)) return false;
+	crypto_box_curve25519xsalsa20poly1305_beforenm(context->send_key, context->remote_pubkey, context->local_seckey_next);
+	context->send_encrypted = true;
+	context->key_updated = false;
+	return true;
+}
+static bool password_hash(unsigned char* output, unsigned char* salt, int saltlength, char* password) {
+	int pwlen = password ? strlen(password) : 0;
+	if (saltlength + pwlen > sizeof(workbuffer)) return errorexit("password and salt too big for buffer");
+	if (saltlength) memmove(workbuffer, salt, saltlength);
+	if (pwlen) memcpy(workbuffer + saltlength, password, pwlen);
+	crypto_hash_sha512(output, workbuffer, saltlength + pwlen);
+	return true;
+}
+static bool process_network_packet(connection_context* context, unsigned char* buffer, int pktlen, int controlflag) {
+	if (context->recv_encrypted) {
+		if (pktlen + 16 > sizeof(workbuffer)) return errorexit("received packet too big for decrypt buffer");
+		memmove(workbuffer + 16, buffer, pktlen);
+		memset(workbuffer, 0, 16);
+		if (crypto_box_curve25519xsalsa20poly1305_open_afternm(workbuffer, workbuffer, pktlen + 16, context->recv_nonce, context->recv_key)) return false;
+		nonceinc(context->recv_nonce);
+		buffer = workbuffer + 32;
+		pktlen -= 16;
+	}
+	if (controlflag) {
+		fprintf(stderr, "Received packet (size=%d type=%d control=%d encrypt=%d)\n", pktlen, buffer[0], controlflag, context->recv_encrypted);
+		if (pktlen < 1) return errorexit("zero length control packet");
+		switch (buffer[0]) {
+			case 0:
+				fprintf(stderr, "Control: tunnel ready\n");
+				context->remote_tunnelready = true;
+				break;
+			case 1:
+				fprintf(stderr, "Control: crypto auth req\n");
+				if (pktlen < 1 + 32 + 24) return errorexit("short control packet 1");
+				if (context->require_key_authentication) {
+					if (memcmp(buffer + 1, context->remote_pubkey_expect, 32)) return errorexit("incorrect crypto auth key");
+					context->require_key_authentication = true;
+				}
+				if (!context->startcryptauthsent) send_start_crypt_auth(context, NULL);
+				if (!send_command_ack(context, 2)) return false;
+				memcpy(context->remote_pubkey, buffer + 1, 32);
+				memcpy(context->send_nonce, buffer + 1 + 32, 12);
+				memcpy(context->recv_nonce + 12, buffer + 1 + 32 + 12, 12);
+				crypto_box_curve25519xsalsa20poly1305_beforenm(context->recv_key, context->remote_pubkey, context->local_seckey_current);
+				crypto_box_curve25519xsalsa20poly1305_beforenm(context->send_key, context->remote_pubkey, context->local_seckey_next);
+				context->recv_encrypted = context->send_encrypted = true;
+				break;
+			case 2:
+				fprintf(stderr, "Control: crypto auth ack\n");
+				memcpy(context->local_seckey_current, context->local_seckey_next, 32);
+				memcpy(context->recv_nonce, context->nonce_next, 12);
+				crypto_box_curve25519xsalsa20poly1305_beforenm(context->recv_key, context->remote_pubkey, context->local_seckey_current);
+				context->recv_encrypted = true;
+				context->key_updated = true;
+				begin_update_key(context, NULL);
+				break;
+			case 3:
+				fprintf(stderr, "Control: key update req\n");
+				if (pktlen < 1 + 32) return errorexit("short control packet 3");
+				if (!send_command_ack(context, 4)) return false;
+				memcpy(context->remote_pubkey, buffer + 1, 32);
+				crypto_box_curve25519xsalsa20poly1305_beforenm(context->recv_key, context->remote_pubkey, context->local_seckey_current);
+				crypto_box_curve25519xsalsa20poly1305_beforenm(context->send_key, context->remote_pubkey, context->local_seckey_next);
+				context->recv_encrypted = context->send_encrypted = true;
+				break;
+			case 4:
+				fprintf(stderr, "Control: key update ack\n");
+				memcpy(context->local_seckey_current, context->local_seckey_next, 32);
+				crypto_box_curve25519xsalsa20poly1305_beforenm(context->recv_key, context->remote_pubkey, context->local_seckey_current);
+				context->recv_encrypted = true;
+				context->key_updated = true;
+				break;
+			case 5:
+				fprintf(stderr, "Control: password authentication req\n");
+				unsigned char pwhash[64];
+				if (!password_hash(pwhash, buffer + 1, pktlen - 1, context->password)) return false;
+				workbuffer[0] = 6;
+				memcpy(workbuffer + 1, pwhash, 64);
+				if (!send_packet(context, workbuffer, 1 + 64, true)) return false;
+				break;
+			case 6:
+				fprintf(stderr, "Control: password authentication ack\n");
+				if (pktlen < 1 + 64) return errorexit("short control packet 6");;
+				char pwhashcheck[64];
+				memcpy(pwhashcheck, buffer + 1, 64);
+				if (!password_hash(pwhash, context->nonce_next, 12, context->password)) return false;
+				if (memcmp(pwhash, pwhashcheck, 64)) return errorexit("incorrect password auth");;
+				context->require_password_authentication = false;
+				break;
+			case 7:
+				fprintf(stderr, "Control: disable encryption req\n");
+				context->recv_encrypted = context->send_encrypted = false;
+				if (!send_command_ack(context, 8)) return false;
+				break;
+			case 8:
+				fprintf(stderr, "Control: disable encryption ack\n");
+				context->recv_encrypted = false;
+				break;
+			case 9:
+				fprintf(stderr, "Control: echo req\n");
+				if (pktlen > sizeof(workbuffer)) return errorexit("echo request too big");;
+				memmove(workbuffer, buffer, pktlen);
+				workbuffer[0] = 10;
+				if (!send_packet(context, workbuffer, pktlen, true)) return false;
+				break;
+			case 10:
+				fprintf(stderr, "Control: echo resp\n");
+				context->pong = true;
+				break;
+			case 13:
+				if (!context->local_tunnelready || !context->remote_tunnelready) return errorexit("received data packet while tunnel not ready");;
+				if (context->tunnel && !tunnel_write_data(context->tunnel, buffer + 1, pktlen - 1)) return false;
+				break;
+			case 11:
+			case 12:
+			case 17:
+			case 81:
+				break;
+			default:
+				fprintf(stderr, "Unknown control type %d\n", buffer[0]);
+				break;
+		}
+	} else {
+		if (!context->local_tunnelready || !context->remote_tunnelready) return errorexit("received data packet while tunnel not ready");;
+		fprintf(stderr, "Tunnel data %d\n", pktlen);
+		if (context->tunnel && !tunnel_write_data(context->tunnel, buffer, pktlen)) return false;
+	}
+	return true;
+}
+
+bool connection_ping(connection_context* context) {
+	if (!context->pong) return false;
+	context->pong = false;
+	send_command_ack(context, 9);
+	return true;
+}
+
+bool connection_init(connection_context* context) {
+	memset(context, 0, sizeof(connection_context));
+	char localpubkey[32];
+	crypto_scalarmult_curve25519_base(context->remote_pubkey, context->local_seckey_current);
+	randombytes(context->nonce_next, 24);
+	context->key_updated = true;
+	context->pong = true;
+	return true;
+}
+bool connection_init_socket(connection_context* context, const int recvsocket, const int sendsocket) {
+	context->recv_socket = recvsocket;
+	context->send_socket = sendsocket;
+	if (!send_packet(context, (unsigned char*)"QUICKTUN", 8, true)) return false;
+	return true;
+}
+bool connection_init_encryption(connection_context* context, const unsigned char* localseckey, const unsigned char* remotepubkey) {
+	if (remotepubkey) {
+		memcpy(context->remote_pubkey_expect, remotepubkey, 32);
+		context->require_key_authentication = true;
+	}
+	context->require_encryption = true;
+	if (!send_start_crypt_auth(context, localseckey)) return false;
+	return true;
+}
+bool connection_init_passwordauth(connection_context* context, char* password) {
+	context->password = password;
+	context->require_password_authentication = true;
+	workbuffer[0] = 5;
+	memcpy(workbuffer + 1, context->nonce_next, 12);
+	if (!send_packet(context, workbuffer, 1 + 12, true)) return false;
+	return true;
+}
+
+bool connection_write_data(connection_context* context, unsigned char* buffer, int len) {
+	return send_packet(context, buffer, len, false);
+}
+
+bool connection_init_done(connection_context* context) {
+	return check_tunnel_ready(context);
+}
+
+bool connection_update_key(connection_context* context) {
+	return begin_update_key(context, NULL);
+}
+